chiark / gitweb /
8be8ae2004bcc13482e154194c99013322ebd360
[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 "tvec.h"
46
47 /*----- Preliminary utilities ---------------------------------------------*/
48
49 #ifdef isnan
50 #  define NANP(x) isnan(x)
51 #else
52 #  define NANP(x) (!((x) == (x)))
53 #endif
54
55 #ifdef isinf
56 #  define INFP(x) isinf(x)
57 #else
58 #  define INFP(x) ((x) > DBL_MAX || (x) < DBL_MIN)
59 #endif
60
61 static int signed_to_buf(buf *b, long i)
62 {
63   kludge64 k;
64   unsigned long u;
65
66   u = i;
67   if (i >= 0) ASSIGN64(k, u);
68   else { ASSIGN64(k, ~u); CPL64(k, k); }
69   return (buf_putk64l(b, k));
70 }
71
72 static int signed_from_buf(buf *b, long *i_out)
73 {
74   kludge64 k, lmax, not_lmin;
75
76   ASSIGN64(lmax, LONG_MAX); ASSIGN64(not_lmin, ~(unsigned long)LONG_MIN);
77   if (buf_getk64l(b, &k)) return (-1);
78   if (CMP64(k, <=, lmax)) *i_out = (long)GET64(unsigned long, k);
79   else {
80     CPL64(k, k);
81     if (CMP64(k, <=, not_lmin)) *i_out = -(long)GET64(unsigned long, k) - 1;
82     else return (-1);
83   }
84   return (0);
85 }
86
87 static int unsigned_to_buf(buf *b, unsigned long u)
88   { kludge64 k; ASSIGN64(k, u); return (buf_putk64l(b, k)); }
89
90 static int unsigned_from_buf(buf *b, unsigned long *u_out)
91 {
92   kludge64 k, ulmax;
93
94   ASSIGN64(ulmax, ULONG_MAX);
95   if (buf_getk64l(b, &k)) return (-1);
96   if (CMP64(k, >, ulmax)) return (-1);
97   *u_out = GET64(unsigned long, k); return (0);
98 }
99
100 static int hex_width(unsigned long u)
101 {
102   int wd;
103   unsigned long t;
104
105   for (t = u >> 4, wd = 4; t >>= wd, wd *= 2, t; );
106   return (wd/4);
107 }
108
109 static int check_signed_range(long i,
110                               const struct tvec_irange *ir,
111                               struct tvec_state *tv)
112 {
113   if (ir && (ir->min > i || i > ir->max)) {
114     tvec_error(tv, "integer %ld out of range (must be in [%ld .. %ld])",
115                i, ir->min, ir->max);
116     return (-1);
117   }
118   return (0);
119 }
120
121 static int check_unsigned_range(unsigned long u,
122                                 const struct tvec_urange *ur,
123                                 struct tvec_state *tv)
124 {
125   if (ur && (ur->min > u || u > ur->max)) {
126     tvec_error(tv, "integer %lu out of range (must be in [%lu .. %lu])",
127                u, ur->min, ur->max);
128     return (-1);
129   }
130   return (0);
131 }
132
133 static int parse_signed(long *i_out, const char *p,
134                         const struct tvec_irange *ir,
135                         struct tvec_state *tv)
136 {
137   char *q; const char *pp;
138   int olderr;
139   long i;
140
141   olderr = errno; errno = 0;
142   pp = p; if (*pp == '-' || *pp == '+') pp++;
143   if (!ISDIGIT(*pp))
144     return (tvec_syntax(tv, *pp ? *pp : fgetc(tv->fp), "signed integer"));
145   i = strtol(p, &q, 0);
146   if (*q && !ISSPACE(*q)) return (tvec_syntax(tv, *q, "end-of-line"));
147   if (errno)
148     return (tvec_error(tv, "invalid integer `%s': %s", p, strerror(errno)));
149   if (check_signed_range(i, ir, tv)) return (-1);
150   errno = olderr; *i_out = i;
151   return (0);
152 }
153
154 static int parse_unsigned(unsigned long *u_out, const char *p,
155                           const struct tvec_urange *ur,
156                           struct tvec_state *tv)
157 {
158   char *q;
159   int olderr;
160   unsigned long u;
161
162   olderr = errno; errno = 0;
163   if (!ISDIGIT(*p)) return (tvec_syntax(tv, *p, "unsigned integer"));
164   u = strtoul(p, &q, 0);
165   if (*q && !ISSPACE(*q)) return (tvec_syntax(tv, *q, "end-of-line"));
166   if (errno)
167     return (tvec_error(tv, "invalid integer `%s': %s", p, strerror(errno)));
168   if (check_unsigned_range(u, ur, tv)) return (-1);
169   errno = olderr; *u_out = u;
170   return (0);
171 }
172
173 static void format_floating(const struct gprintf_ops *gops, void *go,
174                             double x)
175 {
176   int prec;
177
178   if (NANP(x))
179     gprintf(gops, go, "#nan");
180   else if (INFP(x))
181     gprintf(gops, go, x > 0 ? "#+inf" : "#-inf");
182   else {
183     /* Ugh.  C doesn't provide any function for just printing a
184      * floating-point number /correctly/, i.e., so that you can read the
185      * result back and recover the number you first thought of.  There are
186      * complicated algorithms published for doing this, but I really don't
187      * want to get into that here.  So we have this.
188      *
189      * The sign doesn't cause significant difficulty so we're going to ignore
190      * it for now.  So suppose we're given a number %$x = f b^e$%, in
191      * base-%$b$% format, so %$f b^n$% and %$e$% are integers, with
192      * %$0 \le f < 1$%.  We're going to convert it into the nearest integer
193      * of the form %$X = F B^E$%, with similar conditions, only with the
194      * additional requirement that %$X$% is normalized, i.e., that %$X = 0$%
195      * or %$F \ge B^{-N}$%.
196      *
197      * We're rounding to the nearest such %$X$%.  If there is to be ambiguity
198      * in the conversion, then some %$x = f b^e$% and the next smallest
199      * representable number %$x' = x + b^{e-n}$% must both map to the same
200      * %$X$%, which means both %$x$% and %$x'$% must be nearer to %$X$% than
201      * any other number representable in the target system.  The nest larger
202      * number is %$X' = X + B^{E-N}$%; the next smaller number will normally
203      * be %$W = X - B^{E-N}$%, but if %$F = 1/B$ then the next smaller number
204      * is actually %$X - B^{E-N-1}$%.  We ignore this latter possibility in
205      * the pursuit of a conservative estimate (though actually it doesn't
206      * matter).
207      *
208      * If both %$x$% and %$x'$% map to %$X$% then we must have
209      * %$L = X - B^{E-N}/2 \le x$% and %$x + b^{e-n} \le R = X + B^{E-N}/2$%;
210      * so firstly %$f b^e = x \ge L = W + B^{E-N}/2 > W = (F - B^{-N}) B^E$%,
211      * and secondly %$b^{e-n} \le B^{E-N}$%.  Since these inequalities are in
212      * opposite senses, we can divide, giving
213      *
214      *         %$f b^e/b^{e-n} > (F - B^{-N}) B^E/B^{E-N}$% ,
215      *
216      * whence
217      *
218      *         %$f b^n > (F - B^{-N}) B^N = F B^N - 1$% .
219      *
220      * Now %$f \le 1 - b^{-n}$%, and %$F \ge B^{-1}$%, so, for this to be
221      * possible, it must be the case that
222      *
223      *         %$(1 - b^{-n}) b^n = b^n - 1 > B^{N-1} - 1$% .
224      *
225      * Then rearrange and take logarithms, obtaining
226      *
227      *         %$(N - 1) \log B < n \log b$% ,
228      *
229      * and so
230      *
231      *         %$N < n \log b/\log B + 1$% .
232      *
233      * Recall that this is a necessary condition for a collision to occur; we
234      * are therefore safe whenever
235      *
236      *         %$N \ge n \log b/\log B + 1$% ;
237      *
238      * so, taking ceilings,
239      *
240      *         %$N \ge \lceil n \log b/\log B \rceil + 1$% .
241      *
242      * So that's why we have this.
243      *
244      * I'm going to assume that @n = DBL_MANT_DIG@ is sufficiently small that
245      * we can calculate this without ending up on the wrong side of an
246      * integer boundary.
247      *
248      * In C11, we have @DBL_DECIMAL_DIG@, which should be the same value only
249      * as a constant.  Except that modern compilers are more than clever
250      * enough to work out that this is a constant anyway.
251      *
252      * This is sometimes an overestimate: we'll print out meaningless digits
253      * that don't represent anything we actually know about the number in
254      * question.  To fix that, we'd need a complicated algorithm like Steele
255      * and White's Dragon4, Gay's @dtoa@, or Burger and Dybvig's algorithm
256      * (note that Loitsch's Grisu2 is conservative, and Grisu3 hands off to
257      * something else in difficult situations).
258      */
259
260     prec = ceil(DBL_MANT_DIG*log(FLT_RADIX)/log(10)) + 1;
261     gprintf(gops, go, "%.*g", prec, x);
262   }
263 }
264
265 static int eqish_floating_p(double x, double y,
266                             const struct tvec_floatinfo *fi)
267 {
268   double xx, yy, t;
269
270   if (NANP(x)) return (NANP(y)); else if (NANP(y)) return (0);
271   if (INFP(x)) return (x == y); else if (INFP(y)) return (0);
272
273   switch (fi ? fi->f&TVFF_EQMASK : TVFF_EXACT) {
274     case TVFF_EXACT:
275       return (x == y);
276     case TVFF_ABSDELTA:
277       t = x - y; if (t < 0) t = -t; return (t < fi->delta);
278     case TVFF_RELDELTA:
279       xx = x >= 0 ? x : -x; yy = y >= 0 ? y : -y;
280       if (xx < yy) { t = x; x = y; y = t; }
281       return (1.0 - y/x < fi->delta);
282     default:
283       abort();
284   }
285 }
286
287 static int parse_floating(double *x_out, const char *p,
288                           const struct tvec_floatinfo *fi,
289                           struct tvec_state *tv)
290 {
291   const char *pp; char *q;
292   dstr d = DSTR_INIT;
293   double x;
294   int olderr, rc;
295
296   if (STRCMP(p, ==, "#nan")) {
297 #ifdef NAN
298     x = NAN; rc = 0;
299 #else
300     tvec_error(tv, "NaN not supported on this system");
301     rc = -1; goto end;
302 #endif
303   } else if (STRCMP(p, ==, "#inf") ||
304              STRCMP(p, ==, "#+inf") ||
305              STRCMP(p, ==, "+#inf")) {
306 #ifdef NAN
307     x = INFINITY; rc = 0;
308 #else
309     tvec_error(tv, "infinity not supported on this system");
310     rc = -1; goto end;
311 #endif
312   } else if (STRCMP(p, ==, "#-inf") ||
313              STRCMP(p, ==, "-#inf")) {
314 #ifdef NAN
315     x = -INFINITY; rc = 0;
316 #else
317     tvec_error(tv, "infinity not supported on this system");
318     rc = -1; goto end;
319 #endif
320   } else {
321     pp = p;
322     if (*pp == '+' || *pp == '-') pp++;
323     if (*pp == '.') pp++;
324     if (!ISDIGIT(*pp)) {
325       tvec_syntax(tv, *pp ? *pp : fgetc(tv->fp), "floating-point number");
326       rc = -1; goto end;
327     }
328     olderr = errno; errno = 0;
329     x = strtod(p, &q);
330     if (*q) {
331       tvec_syntax(tv, *q, "end-of-line");
332       rc = -1; goto end;
333     }
334     if (errno && (errno != ERANGE || (x > 0 ? -x : x) == HUGE_VAL)) {
335       tvec_error(tv, "invalid floating-point number `%s': %s",
336                  p, strerror(errno));
337       rc = -1; goto end;
338     }
339     errno = olderr;
340   }
341
342   if (NANP(x) && fi && !(fi->f&TVFF_NANOK)) {
343     tvec_error(tv, "#nan not allowed here");
344     rc = -1; goto end;
345   }
346   if (fi && ((!(fi->f&TVFF_NOMIN) && x < fi->min) ||
347              (!(fi->f&TVFF_NOMAX) && x > fi->max))) {
348     dstr_puts(&d, "floating-point number ");
349     format_floating(&dstr_printops, &d, x);
350     dstr_puts(&d, " out of range (must be in ");
351     if (fi->f&TVFF_NOMIN)
352       dstr_puts(&d, "(#-inf");
353     else
354       { dstr_putc(&d, '['); format_floating(&dstr_printops, &d, fi->min); }
355     dstr_puts(&d, " .. ");
356     if (fi->f&TVFF_NOMAX)
357       dstr_puts(&d, "#+inf)");
358     else
359       { format_floating(&dstr_printops, &d, fi->max); dstr_putc(&d, ']'); }
360     dstr_putc(&d, ')'); dstr_putz(&d);
361     tvec_error(tv, "%s", d.buf); rc = -1; goto end;
362   }
363
364   *x_out = x; rc = 0;
365 end:
366   dstr_destroy(&d);
367   return (rc);
368 }
369
370 static int convert_hex(char ch, int *v_out)
371 {
372   if ('0' <= ch && ch <= '9') { *v_out = ch - '0'; return (0); }
373   else if ('a' <= ch && ch <= 'f') { *v_out = ch - 'a' + 10; return (0); }
374   else if ('A' <= ch && ch <= 'F') { *v_out = ch - 'A' + 10; return (0); }
375   else return (-1);
376 }
377
378 static int read_escape(int *ch_out, struct tvec_state *tv)
379 {
380   int ch, i, esc;
381   unsigned f = 0;
382 #define f_brace 1u
383
384   ch = getc(tv->fp);
385   switch (ch) {
386     case EOF: case '\n': tvec_syntax(tv, ch, "string escape");
387     case '\'': *ch_out = '\''; break;
388     case '\\': *ch_out = '\\'; break;
389     case '"': *ch_out = '"'; break;
390     case 'a': *ch_out = '\a'; break;
391     case 'b': *ch_out = '\b'; break;
392     case 'e': *ch_out = '\x1b'; break;
393     case 'f': *ch_out = '\f'; break;
394     case 'n': *ch_out = '\n'; break;
395     case 'r': *ch_out = '\r'; break;
396     case 't': *ch_out = '\t'; break;
397     case 'v': *ch_out = '\v'; break;
398
399     case 'x':
400       ch = getc(tv->fp);
401       if (ch == '{') { f |= f_brace; ch = getc(tv->fp); }
402       else f &= ~f_brace;
403       if (convert_hex(ch, &esc))
404         return (tvec_syntax(tv, ch, "hex digit"));
405       for (;;) {
406         ch = getc(tv->fp); if (convert_hex(ch, &i)) break;
407         esc = 8*esc + i;
408         if (esc > UCHAR_MAX)
409           return (tvec_error(tv,
410                              "character code %d out of range", esc));
411       }
412       if (!(f&f_brace)) ungetc(ch, tv->fp);
413       else if (ch != '}') return (tvec_syntax(tv, ch, "`}'"));
414       *ch_out = esc;
415       break;
416
417     default:
418       if ('0' <= ch && ch < '8') {
419         i = 1; esc = ch - '0';
420         for (;;) {
421           ch = getc(tv->fp);
422           if ('0' > ch || ch >= '8') { ungetc(ch, tv->fp); break; }
423           esc = 8*esc + ch - '0';
424           i++; if (i >= 3) break;
425         }
426         if (esc > UCHAR_MAX)
427           return (tvec_error(tv,
428                              "character code %d out of range", esc));
429         *ch_out = esc;
430       } else
431         return (tvec_syntax(tv, ch, "string escape"));
432   }
433
434   return (0);
435
436 #undef f_brace
437 }
438
439 static int read_quoted_string(dstr *d, int quote, struct tvec_state *tv)
440 {
441   int ch;
442
443   for (;;) {
444     ch = getc(tv->fp);
445     switch (ch) {
446       case EOF: case '\n':
447         return (tvec_syntax(tv, ch, "`%c'", quote));
448       case '\\':
449         if (quote == '\'') goto ordinary;
450         ch = getc(tv->fp); if (ch == '\n') { tv->lno++; break; }
451         ungetc(ch, tv->fp); if (read_escape(&ch, tv)) return (-1);
452         goto ordinary;
453       default:
454         if (ch == quote) goto end;
455       ordinary:
456         DPUTC(d, ch);
457         break;
458     }
459   }
460
461 end:
462   DPUTZ(d);
463   return (0);
464 }
465
466 #define FCF_BRACE 1u
467 static void format_charesc(int ch, unsigned f,
468                            const struct gprintf_ops *gops, void *go)
469 {
470   switch (ch) {
471     case '\a': gprintf(gops, go, "\\a"); break;
472     case '\b': gprintf(gops, go, "\\b"); break;
473     case '\x1b': gprintf(gops, go, "\\e"); break;
474     case '\f': gprintf(gops, go, "\\f"); break;
475     case '\r': gprintf(gops, go, "\\r"); break;
476     case '\n': gprintf(gops, go, "\\n"); break;
477     case '\t': gprintf(gops, go, "\\t"); break;
478     case '\v': gprintf(gops, go, "\\v"); break;
479     case '\'': gprintf(gops, go, "\\'"); break;
480     case '"': gprintf(gops, go, "\\\""); break;
481     default:
482       if (f&FCF_BRACE)
483         gprintf(gops, go, "\\x{%0*x}", hex_width(UCHAR_MAX), ch);
484       else
485         gprintf(gops, go, "\\x%0*x", hex_width(UCHAR_MAX), ch);
486       break;
487   }
488 }
489
490 static void format_char(int ch, const struct gprintf_ops *gops, void *go)
491 {
492   if (ch == EOF)
493     gprintf(gops, go, "#eof");
494   else if (isprint(ch) && ch != '\'')
495     gprintf(gops, go, "'%c'", ch);
496   else {
497     gprintf(gops, go, "'");
498     format_charesc(ch, 0, gops, go);
499     gprintf(gops, go, "'");
500   }
501 }
502
503 enum { TVCODE_BARE, TVCODE_HEX, TVCODE_BASE64, TVCODE_BASE32 };
504
505 static int collect_bare(dstr *d, struct tvec_state *tv)
506 {
507   size_t pos = d->len;
508   enum { WORD, SPACE, ESCAPE }; unsigned s = WORD;
509   int ch, rc;
510
511   for (;;) {
512     ch = getc(tv->fp);
513     switch (ch) {
514       case EOF:
515         tvec_syntax(tv, ch, "bareword");
516         rc = -1; goto end;
517       case '\n':
518         if (s == ESCAPE) { tv->lno++; goto addch; }
519         if (s == WORD) pos = d->len;
520         ungetc(ch, tv->fp); if (tvec_nexttoken(tv)) { rc = -1; goto end; }
521         DPUTC(d, ' '); s = SPACE;
522         break;
523       case '"': case '\'': case '!':
524         if (s == SPACE) { ungetc(ch, tv->fp); goto done; }
525         goto addch;
526       case '\\':
527         s = ESCAPE;
528         break;
529       default:
530         if (s != ESCAPE && isspace(ch)) {
531           if (s == WORD) pos = d->len;
532           DPUTC(d, ch); s = SPACE;
533           break;
534         }
535       addch:
536         DPUTC(d, ch); s = WORD;
537     }
538   }
539
540 done:
541   if (s == SPACE) d->len = pos;
542   DPUTZ(d); rc = 0;
543 end:
544   return (rc);
545 }
546
547 static void set_up_encoding(const codec_class **ccl_out, unsigned *f_out,
548                             unsigned code)
549 {
550   switch (code) {
551     case TVCODE_BARE:
552       *ccl_out = 0; *f_out = 0;
553       break;
554     case TVCODE_HEX:
555       *ccl_out = &hex_class; *f_out = CDCF_IGNCASE;
556       break;
557     case TVCODE_BASE32:
558       *ccl_out = &base32_class; *f_out = CDCF_IGNCASE | CDCF_IGNEQPAD;
559       break;
560     case TVCODE_BASE64:
561       *ccl_out = &base64_class; *f_out = CDCF_IGNEQPAD;
562       break;
563     default:
564       abort();
565   }
566 }
567
568 static int read_compound_string(void **p_inout, size_t *sz_inout,
569                                 unsigned code, struct tvec_state *tv)
570 {
571   const codec_class *ccl; unsigned f;
572   codec *cdc;
573   dstr d = DSTR_INIT, w = DSTR_INIT;
574   char *p;
575   int ch, err, rc;
576
577   set_up_encoding(&ccl, &f, code);
578   if (tvec_nexttoken(tv)) tvec_syntax(tv, fgetc(tv->fp), "string");
579   do {
580     ch = getc(tv->fp);
581     if (ch == '"' || ch == '\'')
582       { if (read_quoted_string(&d, ch, tv)) { rc = -1; goto end; } }
583     else if (ch == '!') {
584       ungetc(ch, tv->fp);
585       DRESET(&w); tvec_readword(tv, &w, ";", "`!'-keyword");
586       if (STRCMP(w.buf, ==, "!bare")) code = TVCODE_BARE;
587       else if (STRCMP(w.buf, ==, "!hex")) code = TVCODE_HEX;
588       else if (STRCMP(w.buf, ==, "!base32")) code = TVCODE_BASE32;
589       else if (STRCMP(w.buf, ==, "!base64")) code = TVCODE_BASE64;
590       else {
591         tvec_error(tv, "unknown string keyword `%s'", w.buf);
592         rc = -1; goto end;
593       }
594       set_up_encoding(&ccl, &f, code);
595     } else if (ccl) {
596       ungetc(ch, tv->fp);
597       DRESET(&w);
598       if (tvec_readword(tv, &w, ";", "%s-encoded fragment", ccl->name))
599         { rc = -1; goto end; }
600       cdc = ccl->decoder(f);
601       err = cdc->ops->code(cdc, w.buf, w.len, &d);
602       if (!err) err = cdc->ops->code(cdc, 0, 0, &d);
603       if (err) {
604         tvec_error(tv, "invalid %s fragment `%s': %s",
605                    ccl->name, w.buf, codec_strerror(err));
606         rc = -1; goto end;
607       }
608       cdc->ops->destroy(cdc);
609     } else switch (code) {
610       case TVCODE_BARE:
611         ungetc(ch, tv->fp);
612         if (collect_bare(&d, tv)) goto done;
613         break;
614       default:
615         abort();
616     }
617   } while (!tvec_nexttoken(tv));
618
619 done:
620   if (*sz_inout <= d.len)
621     { xfree(*p_inout); *p_inout = xmalloc(d.len + 1); }
622   p = *p_inout; memcpy(p, d.buf, d.len); p[d.len] = 0; *sz_inout = d.len;
623   rc = 0;
624 end:
625   dstr_destroy(&d); dstr_destroy(&w);
626   return (rc);
627 }
628
629 /*----- Skeleton ----------------------------------------------------------*/
630 /*
631 static void init_...(union tvec_regval *rv, const struct tvec_regdef *rd)
632 static void release_...(union tvec_regval *rv, const struct tvec_regdef *rd)
633 static int eq_...(const union tvec_regval *rv0, const union tvec_regval *rv1,
634                   const struct tvec_regdef *rd)
635 static int tobuf_...(buf *b, const union tvec_regval *rv,
636                      const struct tvec_regdef *rd)
637 static int frombuf_...(buf *b, union tvec_regval *rv,
638                        const struct tvec_regdef *rd)
639 static int parse_...(union tvec_regval *rv, const struct tvec_regdef *rd,
640                      struct tvec_state *tv)
641 static void dump_...(const union tvec_regval *rv,
642                      const struct tvec_regdef *rd,
643                      struct tvec_state *tv, unsigned style)
644
645 const struct tvec_regty tvty_... = {
646   init_..., release_..., eq_...,
647   tobuf_..., frombuf_...,
648   parse_..., dump_...
649 };
650 */
651 /*----- Signed and unsigned integer types ---------------------------------*/
652
653 static void init_int(union tvec_regval *rv, const struct tvec_regdef *rd)
654   { rv->i = 0; }
655
656 static void init_uint(union tvec_regval *rv, const struct tvec_regdef *rd)
657   { rv->u = 0; }
658
659 static void release_int(union tvec_regval *rv, const struct tvec_regdef *rd)
660   { ; }
661
662 static int eq_int(const union tvec_regval *rv0, const union tvec_regval *rv1,
663                   const struct tvec_regdef *rd)
664   { return (rv0->i == rv1->i); }
665
666 static int eq_uint(const union tvec_regval *rv0,
667                    const union tvec_regval *rv1,
668                    const struct tvec_regdef *rd)
669   { return (rv0->u == rv1->u); }
670
671 static int tobuf_int(buf *b, const union tvec_regval *rv,
672                      const struct tvec_regdef *rd)
673   { return (signed_to_buf(b, rv->i)); }
674
675 static int tobuf_uint(buf *b, const union tvec_regval *rv,
676                        const struct tvec_regdef *rd)
677   { return (unsigned_to_buf(b, rv->u)); }
678
679 static int frombuf_int(buf *b, union tvec_regval *rv,
680                        const struct tvec_regdef *rd)
681   { return (signed_from_buf(b, &rv->i)); }
682
683 static int frombuf_uint(buf *b, union tvec_regval *rv,
684                         const struct tvec_regdef *rd)
685   { return (unsigned_from_buf(b, &rv->u)); }
686
687 static int parse_int(union tvec_regval *rv, const struct tvec_regdef *rd,
688                      struct tvec_state *tv)
689 {
690   dstr d = DSTR_INIT;
691   int rc;
692
693   if (tvec_readword(tv, &d, ";", "signed integer"))
694     { rc = -1; goto end; }
695   if (parse_signed(&rv->i, d.buf, rd->arg.p, tv))
696     { rc = -1; goto end; }
697   if (tvec_flushtoeol(tv, 0))
698     { rc = -1; goto end; }
699   rc = 0;
700 end:
701   dstr_destroy(&d);
702   return (rc);
703 }
704
705 static int parse_uint(union tvec_regval *rv, const struct tvec_regdef *rd,
706                       struct tvec_state *tv)
707 {
708   dstr d = DSTR_INIT;
709   int rc;
710
711   if (tvec_readword(tv, &d, ";", "unsigned integer"))
712     { rc = -1; goto end; }
713   if (parse_unsigned(&rv->u, d.buf, rd->arg.p, tv))
714     { rc = -1; goto end; }
715   if (tvec_flushtoeol(tv, 0))
716     { rc = -1; goto end; }
717   rc = 0;
718 end:
719   dstr_destroy(&d);
720   return (rc);
721 }
722
723 static void dump_int(const union tvec_regval *rv,
724                      const struct tvec_regdef *rd,
725                      unsigned style,
726                      const struct gprintf_ops *gops, void *go)
727 {
728   unsigned long u;
729
730   gprintf(gops, go, "%ld", rv->i);
731   if (!(style&TVSF_COMPACT)) {
732     if (rv->i >= 0) u = rv->i;
733     else u = -(unsigned long)rv->i;
734     gprintf(gops, go, " ; = %s0x%0*lx",
735             rv->i < 0 ? "-" : "", hex_width(u), u);
736     if (rv->i == EOF || (0 <= rv->i && rv->i < UCHAR_MAX))
737       { gprintf(gops, go, " = "); format_char(rv->i, gops, go); }
738   }
739 }
740
741 static void dump_uint(const union tvec_regval *rv,
742                       const struct tvec_regdef *rd,
743                       unsigned style,
744                       const struct gprintf_ops *gops, void *go)
745 {
746   gprintf(gops, go, "%lu", rv->u);
747   if (!(style&TVSF_COMPACT)) {
748     gprintf(gops, go, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
749     if (rv->u < UCHAR_MAX)
750       { gprintf(gops, go, " = "); format_char(rv->u, gops, go); }
751   }
752 }
753
754 const struct tvec_regty tvty_int = {
755   init_int, release_int, eq_int,
756   tobuf_int, frombuf_int,
757   parse_int, dump_int
758 };
759
760 const struct tvec_irange
761   tvrange_schar = { SCHAR_MIN, SCHAR_MAX },
762   tvrange_short = { SHRT_MIN, SHRT_MAX },
763   tvrange_int = { INT_MIN, INT_MAX },
764   tvrange_long = { LONG_MIN, LONG_MAX },
765   tvrange_sbyte = { -128, 127 },
766   tvrange_i16 = { -32768, +32767 },
767   tvrange_i32 = { -2147483648, 2147483647 };
768
769 const struct tvec_regty tvty_uint = {
770   init_uint, release_int, eq_uint,
771   tobuf_uint, frombuf_uint,
772   parse_uint, dump_uint
773 };
774
775 const struct tvec_urange
776   tvrange_uchar = { 0, UCHAR_MAX },
777   tvrange_ushort = { 0, USHRT_MAX },
778   tvrange_uint = { 0, UINT_MAX },
779   tvrange_ulong = { 0, ULONG_MAX },
780   tvrange_size = { 0, (size_t)-1 },
781   tvrange_byte = { 0, 255 },
782   tvrange_u16 = { 0, 65535 },
783   tvrange_u32 = { 0, 4294967296 };
784
785 int tvec_claimeq_int(struct tvec_state *tv, long i0, long i1,
786                      const char *file, unsigned lno, const char *expr)
787 {
788   tv->in[0].v.i = i0; tv->out[0].v.i = i1;
789   return (tvec_claimeq(tv, &tvty_int, 0, file, lno, expr));
790 }
791
792 int tvec_claimeq_uint(struct tvec_state *tv,
793                       unsigned long u0, unsigned long u1,
794                       const char *file, unsigned lno, const char *expr)
795 {
796   tv->in[0].v.u = u0; tv->out[0].v.u = u1;
797   return (tvec_claimeq(tv, &tvty_uint, 0, file, lno, expr));
798 }
799
800 /*----- Main code ---------------------------------------------------------*/
801
802 static void init_float(union tvec_regval *rv, const struct tvec_regdef *rd)
803   { rv->f = 0.0; }
804 static void release_float(union tvec_regval *rv,
805                           const struct tvec_regdef *rd)
806   { ; }
807
808 static int eq_float(const union tvec_regval *rv0,
809                     const union tvec_regval *rv1,
810                     const struct tvec_regdef *rd)
811   { return (eqish_floating_p(rv0->f, rv1->f, rd->arg.p)); }
812
813 static int tobuf_float(buf *b, const union tvec_regval *rv,
814                      const struct tvec_regdef *rd)
815   { return (buf_putf64l(b, rv->f)); }
816 static int frombuf_float(buf *b, union tvec_regval *rv,
817                        const struct tvec_regdef *rd)
818   { return (buf_getf64l(b, &rv->f)); }
819
820 static int parse_float(union tvec_regval *rv, const struct tvec_regdef *rd,
821                        struct tvec_state *tv)
822 {
823   dstr d = DSTR_INIT;
824   int rc;
825
826   if (tvec_readword(tv, &d, ";", "floating-point number"))
827     { rc = -1; goto end; }
828   if (parse_floating(&rv->f, d.buf, rd->arg.p, tv))
829     { rc = -1; goto end; }
830   if (tvec_flushtoeol(tv, 0))
831     { rc = -1; goto end; }
832   rc = 0;
833 end:
834   dstr_destroy(&d);
835   return (rc);
836 }
837
838 static void dump_float(const union tvec_regval *rv,
839                        const struct tvec_regdef *rd,
840                        unsigned style,
841                        const struct gprintf_ops *gops, void *go)
842   { format_floating(gops, go, rv->f); }
843
844 const struct tvec_regty tvty_float = {
845   init_float, release_float, eq_float,
846   tobuf_float, frombuf_float,
847   parse_float, dump_float
848 };
849
850 int tvec_claimeqish_float(struct tvec_state *tv,
851                           double f0, double f1, unsigned f, double delta,
852                           const char *file, unsigned lno,
853                           const char *expr)
854 {
855   struct tvec_floatinfo fi;
856   union tvec_misc arg;
857
858   fi.f = f; fi.min = fi.max = 0.0; fi.delta = delta; arg.p = &fi;
859   tv->in[0].v.f = f0; tv->in[1].v.f = f1;
860   return (tvec_claimeq(tv, &tvty_float, &arg, file, lno, expr));
861 }
862 int tvec_claimeq_float(struct tvec_state *tv,
863                        double f0, double f1,
864                        const char *file, unsigned lno,
865                        const char *expr)
866 {
867   return (tvec_claimeqish_float(tv, f0, f1, TVFF_EXACT, 0.0,
868                                 file, lno, expr));
869 }
870
871 /*----- Enumerations ------------------------------------------------------*/
872
873 static void init_enum(union tvec_regval *rv, const struct tvec_regdef *rd)
874 {
875   const struct tvec_enuminfo *ei = rd->arg.p;
876
877   switch (ei->mv) {
878 #define CASE(tag, ty, slot)                                             \
879         case TVMISC_##tag: rv->slot = 0; break;
880     TVEC_MISCSLOTS(CASE)
881 #undef CASE
882     default: abort();
883   }
884 }
885
886 static int eq_enum(const union tvec_regval *rv0,
887                    const union tvec_regval *rv1,
888                    const struct tvec_regdef *rd)
889 {
890   const struct tvec_enuminfo *ei = rd->arg.p;
891   const struct tvec_fenuminfo *fei;
892
893   switch (ei->mv) {
894 #define CASE(tag, ty, slot)                                             \
895         case TVMISC_##tag: PREP_##tag return (HANDLE_##tag);
896 #define PREP_INT
897 #define HANDLE_INT      rv0->i == rv1->i
898 #define PREP_UINT
899 #define HANDLE_UINT     rv0->u == rv1->u
900 #define PREP_FLT        fei = (const struct tvec_fenuminfo *)ei;
901 #define HANDLE_FLT      eqish_floating_p(rv0->f, rv1->f, fei->fi)
902 #define PREP_PTR
903 #define HANDLE_PTR      rv0->p == rv1->p
904     TVEC_MISCSLOTS(CASE)
905 #undef CASE
906 #undef PREP_INT
907 #undef HANDLE_INT
908 #undef PREP_UINT
909 #undef HANDLE_UINT
910 #undef PREP_FLT
911 #undef HANDLE_FLT
912 #undef PREP_PTR
913 #undef HANDLE_PTR
914     default: abort();
915   }
916 }
917
918 static int tobuf_enum(buf *b, const union tvec_regval *rv,
919                       const struct tvec_regdef *rd)
920 {
921   const struct tvec_enuminfo *ei = rd->arg.p;
922   const struct tvec_penuminfo *pei;
923   const struct tvec_passoc *pa;
924   long i;
925
926   switch (ei->mv) {
927 #define CASE(tag, ty, slot)                                             \
928         case TVMISC_##tag: HANDLE_##tag
929 #define HANDLE_INT      return (signed_to_buf(b, rv->i));
930 #define HANDLE_UINT     return (unsigned_to_buf(b, rv->u));
931 #define HANDLE_FLT      return (buf_putf64l(b, rv->f));
932 #define HANDLE_PTR                                                      \
933         pei = (const struct tvec_penuminfo *)ei;                        \
934         for (pa = pei->av, i = 0; pa->tag; pa++, i++)                   \
935           if (pa->p == rv->p) goto found;                               \
936         if (!rv->p) i = -1;                                             \
937         else return (-1);                                               \
938       found:                                                            \
939         return (signed_to_buf(b, i));
940     TVEC_MISCSLOTS(CASE)
941 #undef CASE
942 #undef HANDLE_INT
943 #undef HANDLE_UINT
944 #undef HANDLE_FLT
945 #undef HANDLE_PTR
946     default: abort();
947   }
948   return (0);
949 }
950
951 static int frombuf_enum(buf *b, union tvec_regval *rv,
952                         const struct tvec_regdef *rd)
953 {
954   const struct tvec_enuminfo *ei = rd->arg.p;
955   const struct tvec_penuminfo *pei;
956   const struct tvec_passoc *pa;
957   long i, n;
958
959   switch (ei->mv) {
960 #define CASE(tag, ty, slot)                                             \
961         case TVMISC_##tag: HANDLE_##tag
962 #define HANDLE_INT      return (signed_from_buf(b, &rv->i));
963 #define HANDLE_UINT     return (unsigned_from_buf(b, &rv->u));
964 #define HANDLE_FLT      return (buf_getf64l(b, &rv->f));
965 #define HANDLE_PTR                                                      \
966         pei = (const struct tvec_penuminfo *)ei;                        \
967         for (pa = pei->av, n = 0; pa->tag; pa++, n++);                  \
968         if (signed_from_buf(b, &i)) return (-1);                        \
969         if (0 <= i && i < n) rv->p = (/*unconst*/ void *)pei->av[i].p;  \
970         else if (i == -1) rv->p = 0;                                    \
971         else return (-1);                                               \
972         return (0);
973     TVEC_MISCSLOTS(CASE)
974 #undef CASE
975 #undef HANDLE_INT
976 #undef HANDLE_UINT
977 #undef HANDLE_FLT
978 #undef HANDLE_PTR
979     default: abort();
980   }
981 }
982
983 static int parse_enum(union tvec_regval *rv, const struct tvec_regdef *rd,
984                       struct tvec_state *tv)
985 {
986   const struct tvec_enuminfo *ei = rd->arg.p;
987 #define DECLS(tag, ty, slot)                                            \
988         const struct tvec_##slot##enuminfo *slot##ei;                   \
989         const struct tvec_##slot##assoc *slot##a;
990   TVEC_MISCSLOTS(DECLS)
991 #undef DECLS
992   dstr d = DSTR_INIT;
993   int rc;
994
995   if (tvec_readword(tv, &d, ";", "enumeration tag or literal integer"))
996     { rc = -1; goto end; }
997   switch (ei->mv) {
998
999 #define CASE(tag_, ty, slot)                                            \
1000         case TVMISC_##tag_:                                             \
1001           slot##ei = (const struct tvec_##slot##enuminfo *)ei;          \
1002           for (slot##a = slot##ei->av; slot##a->tag; slot##a++)         \
1003             if (STRCMP(d.buf, ==, slot##a->tag))                        \
1004               { rv->slot = FETCH_##tag_; goto done; }                   \
1005           HANDLE_##tag_ goto done;
1006
1007 #define FETCH_INT (ia->i)
1008 #define HANDLE_INT                                                      \
1009         if (parse_signed(&rv->i, d.buf, iei->ir, tv))                   \
1010           { rc = -1; goto end; }
1011
1012 #define FETCH_UINT (ua->u)
1013 #define HANDLE_UINT                                                     \
1014         if (parse_unsigned(&rv->u, d.buf, uei->ur, tv))                 \
1015           { rc = -1; goto end; }
1016
1017 #define FETCH_FLT (fa->f)
1018 #define HANDLE_FLT                                                      \
1019         if (parse_floating(&rv->f, d.buf, fei->fi, tv))                 \
1020           { rc = -1; goto end; }
1021
1022 #define FETCH_PTR ((/*unconst*/ void *)(pa->p))
1023 #define HANDLE_PTR                                                      \
1024         if (STRCMP(d.buf, ==, "#nil")) rv->p = 0;                       \
1025         else goto tagonly;
1026
1027     TVEC_MISCSLOTS(CASE)
1028
1029 #undef CASE
1030 #undef FETCH_INT
1031 #undef HANDLE_INT
1032 #undef FETCH_UINT
1033 #undef HANDLE_UINT
1034 #undef FETCH_FLT
1035 #undef HANDLE_FLT
1036 #undef FETCH_PTR
1037 #undef HANDLE_PTR
1038
1039     tagonly:
1040       tvec_error(tv, "unknown `%s' value `%s'", ei->name, d.buf);
1041       rc = -1; goto end;
1042   }
1043
1044 done:
1045   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
1046   rc = 0;
1047 end:
1048   dstr_destroy(&d);
1049   return (rc);
1050 }
1051
1052 static void dump_enum(const union tvec_regval *rv,
1053                       const struct tvec_regdef *rd,
1054                       unsigned style,
1055                       const struct gprintf_ops *gops, void *go)
1056 {
1057   const struct tvec_enuminfo *ei = rd->arg.p;
1058 #define DECLS(tag, ty, slot)                                            \
1059         const struct tvec_##slot##enuminfo *slot##ei;                   \
1060         const struct tvec_##slot##assoc *slot##a;
1061   TVEC_MISCSLOTS(DECLS)
1062 #undef DECLS
1063   const char *tag;
1064   unsigned long u;
1065   unsigned f = 0;
1066 #define f_known 1u
1067
1068   switch (ei->mv) {
1069 #define CASE(tag_, ty, slot)                                            \
1070         case TVMISC_##tag_:                                             \
1071           slot##ei = (const struct tvec_##slot##enuminfo *)ei;          \
1072           for (slot##a = slot##ei->av; slot##a->tag; slot##a++)         \
1073             if (rv->slot == slot##a->slot)                              \
1074               { tag = slot##a->tag; goto found; }                       \
1075           break;
1076     TVEC_MISCSLOTS(CASE)
1077 #undef CASE
1078     default: abort();
1079   }
1080   goto print_raw;
1081
1082 found:
1083   f |= f_known;
1084   gprintf(gops, go, "%s", tag);
1085   if (style&TVSF_COMPACT) return;
1086   gprintf(gops, go, " ; = ");
1087
1088 print_raw:
1089   switch (ei->mv) {
1090 #define CASE(tag, ty, slot)                                             \
1091         case TVMISC_##tag: HANDLE_##tag break;
1092 #define HANDLE_INT      gprintf(gops, go, "%ld", rv->i);
1093 #define HANDLE_UINT     gprintf(gops, go, "%lu", rv->u);
1094 #define HANDLE_FLT      format_floating(gops, go, rv->f);
1095 #define HANDLE_PTR      if (!rv->p) gprintf(gops, go, "#nil");          \
1096                         else gprintf(gops, go, "#<%s %p>", ei->name, rv->p);
1097     TVEC_MISCSLOTS(CASE)
1098 #undef CASE
1099 #undef HANDLE_INT
1100 #undef HANDLE_UINT
1101 #undef HANDLE_FLT
1102 #undef HANDLE_PTR
1103   }
1104
1105   if (style&TVSF_COMPACT) return;
1106   switch (ei->mv) {
1107     case TVMISC_INT:
1108       if (!(f&f_known)) gprintf(gops, go, " ;");
1109       if (rv->i >= 0) u = rv->i;
1110       else u = -(unsigned long)rv->i;
1111       gprintf(gops, go, " = %s0x%0*lx",
1112               rv->i < 0 ? "-" : "", hex_width(u), u);
1113       if (rv->i == EOF || (0 <= rv->i && rv->i < UCHAR_MAX))
1114         { gprintf(gops, go, " = "); format_char(rv->i, gops, go); }
1115       break;
1116     case TVMISC_UINT:
1117       if (!(f&f_known)) gprintf(gops, go, " ;");
1118       gprintf(gops, go, " = 0x%0*lx", hex_width(rv->u), rv->u);
1119       if (rv->u < UCHAR_MAX)
1120         { gprintf(gops, go, " = "); format_char(rv->u, gops, go); }
1121       break;
1122   }
1123 }
1124
1125 const struct tvec_regty tvty_enum = {
1126   init_enum, release_int, eq_enum,
1127   tobuf_enum, frombuf_enum,
1128   parse_enum, dump_enum
1129 };
1130
1131 static const struct tvec_iassoc bool_assoc[] = {
1132   { "nil",              0 },
1133   { "false",            0 },
1134   { "f",                0 },
1135   { "no",               0 },
1136   { "n",                0 },
1137   { "off",              0 },
1138
1139   { "t",                1 },
1140   { "true",             1 },
1141   { "yes",              1 },
1142   { "y",                1 },
1143   { "on",               1 },
1144
1145   { 0,                  0 }
1146 };
1147
1148 const struct tvec_ienuminfo tvenum_bool =
1149   { { "bool", TVMISC_INT }, bool_assoc, &tvrange_int };
1150
1151 #define DEFCLAIM(tag, ty, slot)                                         \
1152         int tvec_claimeq_##slot##enum                                   \
1153           (struct tvec_state *tv,                                       \
1154            const struct tvec_##slot##enuminfo *ei, ty e0, ty e1,        \
1155            const char *file, unsigned lno, const char *expr)            \
1156         {                                                               \
1157           union tvec_misc arg;                                          \
1158                                                                         \
1159           assert(ei->_ei.mv == TVMISC_##tag);                           \
1160           arg.p = ei;                                                   \
1161           tv->in[0].v.slot = GET_##tag(e0);                             \
1162           tv->out[0].v.slot = GET_##tag(e1);                            \
1163           return (tvec_claimeq(tv, &tvty_enum, &arg, file, lno, expr)); \
1164         }
1165 #define GET_INT(e) (e)
1166 #define GET_UINT(e) (e)
1167 #define GET_FLT(e) (e)
1168 #define GET_PTR(e) ((/*unconst*/ void *)(e))
1169 TVEC_MISCSLOTS(DEFCLAIM)
1170 #undef DEFCLAIM
1171 #undef GET_INT
1172 #undef GET_UINT
1173 #undef GET_FLT
1174 #undef GET_PTR
1175
1176 /*----- Flag types --------------------------------------------------------*/
1177
1178 static int parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd,
1179                        struct tvec_state *tv)
1180 {
1181   const struct tvec_flaginfo *fi = rd->arg.p;
1182   const struct tvec_flag *f;
1183   unsigned long m = 0, v = 0, t;
1184   dstr d = DSTR_INIT;
1185   int ch, rc;
1186
1187   for (;;) {
1188     DRESET(&d);
1189     if (tvec_readword(tv, &d, "|;", "flag name or integer"))
1190       { rc = -1; goto end; }
1191
1192     for (f = fi->fv; f->tag; f++)
1193       if (STRCMP(f->tag, ==, d.buf)) {
1194         if (m&f->m)
1195           { tvec_error(tv, "colliding flag setting"); rc = -1; goto end; }
1196         else
1197           { m |= f->m; v |= f->v; goto next; }
1198       }
1199
1200     if (parse_unsigned(&t, d.buf, fi->range, tv))
1201       { rc = -1; goto end; }
1202     v |= t;
1203   next:
1204     if (tvec_nexttoken(tv)) break;
1205     ch = getc(tv->fp);
1206       if (ch != '|') { tvec_syntax(tv, ch, "`|'"); rc = -1; goto end; }
1207     if (tvec_nexttoken(tv))
1208       { tvec_syntax(tv, '\n', "flag name or integer"); rc = -1; goto end; }
1209   }
1210   rv->u = v;
1211   rc = 0;
1212 end:
1213   dstr_destroy(&d);
1214   return (rc);
1215 }
1216
1217 static void dump_flags(const union tvec_regval *rv,
1218                        const struct tvec_regdef *rd,
1219                        unsigned style,
1220                        const struct gprintf_ops *gops, void *go)
1221 {
1222   const struct tvec_flaginfo *fi = rd->arg.p;
1223   const struct tvec_flag *f;
1224   unsigned long m = ~(unsigned long)0, v = rv->u;
1225   const char *sep;
1226
1227   for (f = fi->fv, sep = ""; f->tag; f++)
1228     if ((m&f->m) && (v&f->m) == f->v) {
1229       gprintf(gops, go, "%s%s", sep, f->tag); m &= ~f->m;
1230       sep = style&TVSF_COMPACT ? "|" : " | ";
1231     }
1232
1233   if (v&m) gprintf(gops, go, "%s0x%0*lx", sep, hex_width(v), v&m);
1234
1235   if (!(style&TVSF_COMPACT))
1236     gprintf(gops, go, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
1237 }
1238
1239 const struct tvec_regty tvty_flags = {
1240   init_uint, release_int, eq_uint,
1241   tobuf_uint, frombuf_uint,
1242   parse_flags, dump_flags
1243 };
1244
1245 int tvec_claimeq_flags(struct tvec_state *tv,
1246                        const struct tvec_flaginfo *fi,
1247                        unsigned long f0, unsigned long f1,
1248                        const char *file, unsigned lno, const char *expr)
1249 {
1250   union tvec_misc arg;
1251
1252   arg.p = fi; tv->in[0].v.u = f0; tv->out[0].v.u = f1;
1253   return (tvec_claimeq(tv, &tvty_flags, &arg, file, lno, expr));
1254 }
1255
1256 /*----- Characters --------------------------------------------------------*/
1257
1258 static int tobuf_char(buf *b, const union tvec_regval *rv,
1259                      const struct tvec_regdef *rd)
1260 {
1261   uint32 u;
1262   if (0 <= rv->i && rv->i <= UCHAR_MAX) u = rv->i;
1263   else if (rv->i == EOF) u = MASK32;
1264   else return (-1);
1265   return (buf_putu32l(b, u));
1266 }
1267
1268 static int frombuf_char(buf *b, union tvec_regval *rv,
1269                        const struct tvec_regdef *rd)
1270 {
1271   uint32 u;
1272
1273   if (buf_getu32l(b, &u)) return (-1);
1274   if (0 <= u && u <= UCHAR_MAX) rv->i = u;
1275   else if (u == MASK32) rv->i = EOF;
1276   else return (-1);
1277   return (0);
1278 }
1279
1280 static int parse_char(union tvec_regval *rv, const struct tvec_regdef *rd,
1281                       struct tvec_state *tv)
1282 {
1283   dstr d = DSTR_INIT;
1284   int ch, rc;
1285   unsigned f = 0;
1286 #define f_quote 1u
1287
1288   ch = getc(tv->fp);
1289   if (ch == '#') {
1290     ungetc(ch, tv->fp);
1291     if (tvec_readword(tv, &d, ";", "character name")) { rc = -1; goto end; }
1292     if (STRCMP(d.buf, ==, "#eof"))
1293       rv->i = EOF;
1294     else {
1295       rc = tvec_error(tv, "unknown character name `%s'", d.buf);
1296       goto end;
1297     }
1298     rc = 0; goto end;
1299   }
1300
1301   if (ch == '\'') { f |= f_quote; ch = getc(tv->fp); }
1302   switch (ch) {
1303     case '\'':
1304       if (!(f&f_quote)) goto plain;
1305     case EOF: case '\n':
1306       rc = tvec_syntax(tv, ch, "character"); goto end;
1307     case '\\':
1308       if (read_escape(&ch, tv)) return (-1);
1309     default: plain:
1310       rv->i = ch; break;
1311   }
1312   if (f&f_quote) {
1313     ch = getc(tv->fp);
1314     if (ch != '\'') { rc = tvec_syntax(tv, ch, "`''"); goto end; }
1315   }
1316   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
1317   rc = 0;
1318 end:
1319   dstr_destroy(&d);
1320   return (rc);
1321
1322 #undef f_quote
1323 }
1324
1325 static void dump_char(const union tvec_regval *rv,
1326                       const struct tvec_regdef *rd,
1327                       unsigned style,
1328                       const struct gprintf_ops *gops, void *go)
1329 {
1330   unsigned u;
1331
1332   if ((style&TVSF_COMPACT) && isprint(rv->i) && rv->i != '\'')
1333     gprintf(gops, go, "%c", (int)rv->i);
1334   else
1335     format_char(rv->i, gops, go);
1336
1337   if (!(style&TVSF_COMPACT)) {
1338     u = rv->i < 0 ? -rv->i : rv->i;
1339     gprintf(gops, go, " ; = %d = %s0x%0*x",
1340             (int)rv->i, rv->i < 0 ? "-" : "", hex_width(u), u);
1341   }
1342 }
1343
1344 const struct tvec_regty tvty_char = {
1345   init_int, release_int, eq_int,
1346   tobuf_char, frombuf_char,
1347   parse_char, dump_char
1348 };
1349
1350 int tvec_claimeq_char(struct tvec_state *tv, int c0, int c1,
1351                       const char *file, unsigned lno, const char *expr)
1352 {
1353   tv->in[0].v.i = c0; tv->out[0].v.i = c1;
1354   return (tvec_claimeq(tv, &tvty_char, 0, file, lno, expr));
1355 }
1356
1357 /*----- Text and byte strings ---------------------------------------------*/
1358
1359 void tvec_allocstring(union tvec_regval *rv, size_t sz)
1360 {
1361   if (rv->str.sz < sz) { xfree(rv->str.p); rv->str.p = xmalloc(sz); }
1362   rv->str.sz = sz;
1363 }
1364
1365 void tvec_allocbytes(union tvec_regval *rv, size_t sz)
1366 {
1367   if (rv->bytes.sz < sz) { xfree(rv->bytes.p); rv->bytes.p = xmalloc(sz); }
1368   rv->bytes.sz = sz;
1369 }
1370
1371 static void init_string(union tvec_regval *rv, const struct tvec_regdef *rd)
1372   { rv->str.p = 0; rv->str.sz = 0; }
1373
1374 static void init_bytes(union tvec_regval *rv, const struct tvec_regdef *rd)
1375   { rv->bytes.p = 0; rv->bytes.sz = 0; }
1376
1377 static void release_string(union tvec_regval *rv,
1378                           const struct tvec_regdef *rd)
1379   { xfree(rv->str.p); }
1380
1381 static void release_bytes(union tvec_regval *rv,
1382                           const struct tvec_regdef *rd)
1383   { xfree(rv->bytes.p); }
1384
1385 static int eq_string(const union tvec_regval *rv0,
1386                      const union tvec_regval *rv1,
1387                      const struct tvec_regdef *rd)
1388 {
1389   return (rv0->str.sz == rv1->str.sz &&
1390           (!rv0->bytes.sz ||
1391            MEMCMP(rv0->str.p, ==, rv1->str.p, rv1->str.sz)));
1392 }
1393
1394 static int eq_bytes(const union tvec_regval *rv0,
1395                     const union tvec_regval *rv1,
1396                     const struct tvec_regdef *rd)
1397 {
1398   return (rv0->bytes.sz == rv1->bytes.sz &&
1399           (!rv0->bytes.sz ||
1400            MEMCMP(rv0->bytes.p, ==, rv1->bytes.p, rv1->bytes.sz)));
1401 }
1402
1403 static int tobuf_string(buf *b, const union tvec_regval *rv,
1404                         const struct tvec_regdef *rd)
1405   { return (buf_putmem32l(b, rv->str.p, rv->str.sz)); }
1406
1407 static int tobuf_bytes(buf *b, const union tvec_regval *rv,
1408                        const struct tvec_regdef *rd)
1409   { return (buf_putmem32l(b, rv->bytes.p, rv->bytes.sz)); }
1410
1411 static int frombuf_string(buf *b, union tvec_regval *rv,
1412                           const struct tvec_regdef *rd)
1413 {
1414   const void *p;
1415   size_t sz;
1416
1417   p = buf_getmem32l(b, &sz); if (!p) return (-1);
1418   tvec_allocstring(rv, sz); memcpy(rv->str.p, p, sz);
1419   return (0);
1420 }
1421
1422 static int frombuf_bytes(buf *b, union tvec_regval *rv,
1423                          const struct tvec_regdef *rd)
1424 {
1425   const void *p;
1426   size_t sz;
1427
1428   p = buf_getmem32l(b, &sz); if (!p) return (-1);
1429   tvec_allocbytes(rv, sz); memcpy(rv->bytes.p, p, sz);
1430   return (0);
1431 }
1432
1433 static int check_string_length(size_t sz, const struct tvec_urange *ur,
1434                                struct tvec_state *tv)
1435 {
1436   if (ur && (ur->min > sz || sz > ur->max))
1437     return (tvec_error(tv,
1438                        "invalid string length %lu; must be in [%lu..%lu]",
1439                        (unsigned long)sz, ur->min, ur->max));
1440   return (0);
1441 }
1442
1443 static int parse_string(union tvec_regval *rv, const struct tvec_regdef *rd,
1444                         struct tvec_state *tv)
1445 {
1446   void *p = rv->str.p;
1447
1448   if (read_compound_string(&p, &rv->str.sz, TVCODE_BARE, tv)) return (-1);
1449   rv->str.p = p;
1450   if (check_string_length(rv->str.sz, rd->arg.p, tv)) return (-1);
1451   return (0);
1452 }
1453
1454 static int parse_bytes(union tvec_regval *rv, const struct tvec_regdef *rd,
1455                        struct tvec_state *tv)
1456 {
1457   void *p = rv->bytes.p;
1458
1459   if (read_compound_string(&p, &rv->bytes.sz, TVCODE_HEX, tv)) return (-1);
1460   rv->bytes.p = p;
1461   if (check_string_length(rv->bytes.sz, rd->arg.p, tv)) return (-1);
1462   return (0);
1463 }
1464
1465 static void dump_string(const union tvec_regval *rv,
1466                         const struct tvec_regdef *rd,
1467                         unsigned style,
1468                         const struct gprintf_ops *gops, void *go)
1469 {
1470   const unsigned char *p, *q, *l;
1471   unsigned f = 0;
1472 #define f_nonword 1u
1473 #define f_newline 2u
1474
1475   if (!rv->str.sz) { gprintf(gops, go, "\"\""); return; }
1476
1477   p = (const unsigned char *)rv->str.p; l = p + rv->str.sz;
1478   if (*p == '!' || *p == ';' || *p == '"' || *p == '\'') goto quote;
1479   for (q = p; q < l; q++)
1480     if (*q == '\n' && q != l - 1) f |= f_newline;
1481     else if (!*q || !isgraph(*q) || *q == '\\') f |= f_nonword;
1482   if (f&f_newline) { gprintf(gops, go, "\n\t"); goto quote; }
1483   else if (f&f_nonword) goto quote;
1484   gops->putm(go, (const char *)p, rv->str.sz); return;
1485
1486 quote:
1487   gprintf(gops, go, "\"");
1488   for (q = p; q < l; q++)
1489     if (!isprint(*q) || *q == '"') {
1490       if (p < q) gops->putm(go, (const char *)p, q - p);
1491       if (*q == '\n' && !(style&TVSF_COMPACT))gprintf(gops, go, "\\n\"\t\"");
1492       else format_charesc(*q, FCF_BRACE, gops, go);
1493     }
1494   if (p < q) gops->putm(go, (const char *)p, q - p);
1495   gprintf(gops, go, "\"");
1496
1497 #undef f_nonword
1498 #undef f_newline
1499 }
1500
1501 static void dump_bytes(const union tvec_regval *rv,
1502                        const struct tvec_regdef *rd,
1503                        unsigned style,
1504                        const struct gprintf_ops *gops, void *go)
1505 {
1506   const unsigned char *p = rv->bytes.p, *l = p + rv->bytes.sz;
1507   size_t off, sz = rv->bytes.sz;
1508   unsigned i, n;
1509   int wd;
1510
1511   if (!sz) {
1512     gprintf(gops, go, style&TVSF_COMPACT ? "\"\"" : "\"\" ; empty");
1513     return;
1514   }
1515
1516   if (style&TVSF_COMPACT) {
1517     while (p < l) gprintf(gops, go, "%02x", *p++);
1518     return;
1519   }
1520
1521   if (sz > 16) gprintf(gops, go, "\n\t");
1522
1523   off = 0; wd = hex_width(sz);
1524   while (p < l) {
1525     if (l - p < 16) n = l - p;
1526     else n = 16;
1527
1528     for (i = 0; i < 16; i++) {
1529       if (i < n) gprintf(gops, go, "%02x", p[i]);
1530       else gprintf(gops, go, "  ");
1531       if (i%4 == 3) gprintf(gops, go, " ");
1532     }
1533     gprintf(gops, go, " ; ");
1534     if (sz > 16) gprintf(gops, go, "[%0*lx] ", wd, (unsigned long)off);
1535     for (i = 0; i < n; i++)
1536       gprintf(gops, go, "%c", isprint(p[i]) ? p[i] : '.');
1537     p += n; off += n;
1538     if (p < l) gprintf(gops, go, "\n\t");
1539   }
1540 }
1541
1542 const struct tvec_regty tvty_string = {
1543   init_string, release_string, eq_string,
1544   tobuf_string, frombuf_string,
1545   parse_string, dump_string
1546 };
1547
1548 const struct tvec_regty tvty_bytes = {
1549   init_bytes, release_bytes, eq_bytes,
1550   tobuf_bytes, frombuf_bytes,
1551   parse_bytes, dump_bytes
1552 };
1553
1554 int tvec_claimeq_string(struct tvec_state *tv,
1555                         const char *p0, size_t sz0,
1556                         const char *p1, size_t sz1,
1557                         const char *file, unsigned lno, const char *expr)
1558 {
1559   tv->in[0].v.str.p = (/*unconst*/ char *)p0; tv->in[0].v.str.sz = sz0;
1560   tv->out[0].v.str.p =(/*unconst*/ char *) p1; tv->out[0].v.str.sz = sz1;
1561   return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
1562 }
1563
1564 int tvec_claimeq_strz(struct tvec_state *tv,
1565                       const char *p0, const char *p1,
1566                       const char *file, unsigned lno, const char *expr)
1567 {
1568   tv->in[0].v.str.p = (/*unconst*/ char *)p0;
1569     tv->in[0].v.str.sz = strlen(p0);
1570   tv->out[0].v.str.p = (/*unconst*/ char *)p1;
1571     tv->out[0].v.str.sz = strlen(p1);
1572   return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
1573 }
1574
1575 int tvec_claimeq_bytes(struct tvec_state *tv,
1576                        const void *p0, size_t sz0,
1577                        const void *p1, size_t sz1,
1578                        const char *file, unsigned lno, const char *expr)
1579 {
1580   tv->in[0].v.bytes.p = (/*unconst*/ void *)p0;
1581     tv->in[0].v.bytes.sz = sz0;
1582   tv->out[0].v.bytes.p = (/*unconst*/ void *)p1;
1583     tv->out[0].v.bytes.sz = sz1;
1584   return (tvec_claimeq(tv, &tvty_bytes, 0, file, lno, expr));
1585 }
1586
1587 /*----- Buffer type -------------------------------------------------------*/
1588
1589 static int eq_buffer(const union tvec_regval *rv0,
1590                      const union tvec_regval *rv1,
1591                      const struct tvec_regdef *rd)
1592   { return (rv0->bytes.sz == rv1->bytes.sz); }
1593
1594 static int tobuf_buffer(buf *b, const union tvec_regval *rv,
1595                          const struct tvec_regdef *rd)
1596   { return (unsigned_to_buf(b, rv->bytes.sz)); }
1597
1598 static int frombuf_buffer(buf *b, union tvec_regval *rv,
1599                           const struct tvec_regdef *rd)
1600 {
1601   unsigned long u;
1602
1603   if (unsigned_from_buf(b, &u)) return (-1);
1604   if (u > (size_t)-1) return (-1);
1605   tvec_allocbytes(rv, u); memset(rv->bytes.p, '!', u);
1606   return (0);
1607 }
1608
1609 static const char units[] = "kMGTPEZY";
1610
1611 static int parse_buffer(union tvec_regval *rv,
1612                         const struct tvec_regdef *rd,
1613                         struct tvec_state *tv)
1614 {
1615   dstr d = DSTR_INIT;
1616   char *q; const char *unit;
1617   int olderr;
1618   size_t pos;
1619   unsigned long u, t;
1620   int rc;
1621   unsigned f = 0;
1622 #define f_range 1u
1623
1624   if (tvec_readword(tv, &d, ";", "buffer length")) { rc = -1; goto end; }
1625   olderr = errno; errno = 0;
1626   u = strtoul(d.buf, &q, 0);
1627   if (errno) goto bad;
1628   errno = olderr;
1629   if (!*q) {
1630     tvec_skipspc(tv); pos = d.len;
1631     if (!tvec_readword(tv, &d, ";", 0)) pos++;
1632     q = d.buf + pos;
1633   }
1634
1635   if (u > (size_t)-1) goto rangerr;
1636   for (t = u, unit = units; *unit; unit++) {
1637     if (t > (size_t)-1/1024) f |= f_range;
1638     else t *= 1024;
1639     if (*q == *unit && (!q[1] || q[1] == 'B')) {
1640       if (f&f_range) goto rangerr;
1641       u = t; q += 2; break;
1642     }
1643   }
1644   if (*q && *q != ';') goto bad;
1645   if (check_string_length(u, rd->arg.p, tv)) { rc = -1; goto end; }
1646
1647   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
1648   tvec_allocbytes(rv, u); memset(rv->bytes.p, '?', u);
1649   rc = 0;
1650 end:
1651   DDESTROY(&d); return (rc);
1652
1653 bad:
1654   tvec_error(tv, "invalid buffer length `%s'", d.buf);
1655   rc = -1; goto end;
1656
1657 rangerr:
1658   tvec_error(tv, "buffer length `%s' out of range", d.buf);
1659   rc = -1; goto end;
1660
1661 #undef f_range
1662 }
1663
1664 static void dump_buffer(const union tvec_regval *rv,
1665                         const struct tvec_regdef *rd,
1666                         unsigned style,
1667                         const struct gprintf_ops *gops, void *go)
1668 {
1669   const char *unit;
1670   unsigned long u = rv->bytes.sz;
1671
1672   if (!u || u%1024)
1673     gprintf(gops, go, "%lu B", u);
1674   else {
1675     for (unit = units, u /= 1024; !(u%1024) && unit[1]; u /= 1024, unit++);
1676     gprintf(gops, go, "%lu %cB", u, *unit);
1677   }
1678 }
1679
1680 const struct tvec_regty tvty_buffer = {
1681   init_bytes, release_bytes, eq_buffer,
1682   tobuf_buffer, frombuf_buffer,
1683   parse_buffer, dump_buffer
1684 };
1685
1686 /*----- That's all, folks -------------------------------------------------*/