chiark / gitweb /
@@@ adjust bench timings
[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 <limits.h>
34 #include <stdio.h>
35 #include <string.h>
36
37 #include "buf.h"
38 #include "codec.h"
39 #  include "base32.h"
40 #  include "base64.h"
41 #  include "hex.h"
42 #include "dstr.h"
43 #include "tvec.h"
44
45 /*----- Preliminary utilities ---------------------------------------------*/
46
47 static int signed_to_buf(buf *b, long i)
48 {
49   kludge64 k;
50   unsigned long u;
51
52   u = i;
53   if (i >= 0) ASSIGN64(k, u);
54   else { ASSIGN64(k, ~u); CPL64(k, k); }
55   return (buf_putk64l(b, k));
56 }
57
58 static int signed_from_buf(buf *b, long *i_out)
59 {
60   kludge64 k, lmax, not_lmin;
61
62   ASSIGN64(lmax, LONG_MAX); ASSIGN64(not_lmin, ~(unsigned long)LONG_MIN);
63   if (buf_getk64l(b, &k)) return (-1);
64   if (CMP64(k, <=, lmax)) *i_out = (long)GET64(unsigned long, k);
65   else {
66     CPL64(k, k);
67     if (CMP64(k, <=, not_lmin)) *i_out = -(long)GET64(unsigned long, k) - 1;
68     else return (-1);
69   }
70   return (0);
71 }
72
73 static int unsigned_to_buf(buf *b, unsigned long u)
74   { kludge64 k; ASSIGN64(k, u); return (buf_putk64l(b, k)); }
75
76 static int unsigned_from_buf(buf *b, unsigned long *u_out)
77 {
78   kludge64 k, ulmax;
79
80   ASSIGN64(ulmax, ULONG_MAX);
81   if (buf_getk64l(b, &k)) return (-1);
82   if (CMP64(k, >, ulmax)) return (-1);
83   *u_out = GET64(unsigned long, k); return (0);
84 }
85
86 static int hex_width(unsigned long u)
87 {
88   int wd;
89   unsigned long t;
90
91   for (t = u >> 4, wd = 4; t >>= wd, wd *= 2, t; );
92   return (wd/4);
93 }
94
95 static int check_signed_range(long i,
96                               const struct tvec_irange *ir,
97                               struct tvec_state *tv)
98 {
99   if (ir && (ir->min > i || i > ir->max)) {
100     tvec_error(tv, "integer %ld out of range (must be in [%ld .. %ld])",
101                i, ir->min, ir->max);
102     return (-1);
103   }
104   return (0);
105 }
106
107 static int check_unsigned_range(unsigned long u,
108                                 const struct tvec_urange *ur,
109                                 struct tvec_state *tv)
110 {
111   if (ur && (ur->min > u || u > ur->max)) {
112     tvec_error(tv, "integer %lu out of range (must be in [%lu .. %lu])",
113                u, ur->min, ur->max);
114     return (-1);
115   }
116   return (0);
117 }
118
119 static int parse_signed(long *i_out, const char *p,
120                         const struct tvec_irange *ir,
121                         struct tvec_state *tv)
122 {
123   char *q; const char *pp;
124   int olderr;
125   long i;
126
127   olderr = errno; errno = 0;
128   pp = p; if (*pp == '-' || *pp == '+') pp++;
129   if (!ISDIGIT(*pp)) return (tvec_syntax(tv, *pp, "signed integer"));
130   i = strtol(p, &q, 0);
131   if (*q && !ISSPACE(*q)) return (tvec_syntax(tv, *q, "end-of-line"));
132   if (errno) return (tvec_error(tv, "invalid integer `%s'", p));
133   check_signed_range(i, ir, tv);
134   errno = olderr; *i_out = i;
135   return (0);
136 }
137
138 static int parse_unsigned(unsigned long *u_out, const char *p,
139                           const struct tvec_urange *ur,
140                           struct tvec_state *tv)
141 {
142   char *q;
143   int olderr;
144   unsigned long u;
145
146   olderr = errno; errno = 0;
147   if (!ISDIGIT(*p)) return (tvec_syntax(tv, *p, "unsigned integer"));
148   u = strtoul(p, &q, 0);
149   if (*q && !ISSPACE(*q)) return (tvec_syntax(tv, *q, "end-of-line"));
150   if (errno) return (tvec_error(tv, "invalid integer `%s'", p));
151   check_unsigned_range(u, ur, tv);
152   errno = olderr; *u_out = u;
153   return (0);
154 }
155
156 static int convert_hex(char ch, int *v_out)
157 {
158   if ('0' <= ch && ch <= '9') { *v_out = ch - '0'; return (0); }
159   else if ('a' <= ch && ch <= 'f') { *v_out = ch - 'a' + 10; return (0); }
160   else if ('A' <= ch && ch <= 'F') { *v_out = ch - 'A' + 10; return (0); }
161   else return (-1);
162 }
163
164 static int read_quoted_string(dstr *d, int quote, struct tvec_state *tv)
165 {
166   char expect[4];
167   int ch, i, esc;
168   unsigned f = 0;
169 #define f_brace 1u
170
171   sprintf(expect, "`%c'", quote);
172
173   for (;;) {
174     ch = getc(tv->fp);
175   reinsert:
176     switch (ch) {
177       case EOF: case '\n':
178         return (tvec_syntax(tv, ch, expect));
179
180       case '\\':
181         if (quote == '\'') goto ordinary;
182         ch = getc(tv->fp);
183         switch (ch) {
184           case EOF: tvec_syntax(tv, ch, expect);
185           case '\n': tv->lno++; break;
186           case '\'': DPUTC(d, '\''); break;
187           case '\\': DPUTC(d, '\\'); break;
188           case '"': DPUTC(d, '"'); break;
189           case 'a': DPUTC(d, '\a'); break;
190           case 'b': DPUTC(d, '\b'); break;
191           case 'e': DPUTC(d, '\x1b'); break;
192           case 'f': DPUTC(d, '\f'); break;
193           case 'n': DPUTC(d, '\n'); break;
194           case 'r': DPUTC(d, '\r'); break;
195           case 't': DPUTC(d, '\t'); break;
196           case 'v': DPUTC(d, '\v'); break;
197
198           case 'x':
199             ch = getc(tv->fp);
200             if (ch == '{') { f |= f_brace; ch = getc(tv->fp); }
201             else f &= ~f_brace;
202             if (convert_hex(ch, &esc))
203               return (tvec_syntax(tv, ch, "hex digit"));
204             for (;;) {
205               ch = getc(tv->fp); if (convert_hex(ch, &i)) break;
206               esc = 8*esc + i;
207               if (esc > UCHAR_MAX)
208                 return (tvec_error(tv,
209                                    "character code %d out of range", esc));
210             }
211             DPUTC(d, esc);
212             if (!(f&f_brace)) goto reinsert;
213             else if (ch != '}') return (tvec_syntax(tv, ch, "`}'"));
214             break;
215
216           default:
217             if ('0' <= ch && ch < '8') {
218               i = 1; esc = ch - '0';
219               for (;;) {
220                 ch = getc(tv->fp);
221                 if (i > 3 || '0' > ch || ch >= '8') break;
222                 esc = 8*esc + ch - '0'; i++;
223               }
224               if (esc > UCHAR_MAX)
225                 return (tvec_error(tv,
226                                    "character code %d out of range", esc));
227               DPUTC(d, esc);
228               goto reinsert;
229             }
230             return (tvec_syntax(tv, ch, "string escape"));
231         }
232         break;
233
234       default:
235         if (ch == quote) goto end;
236       ordinary:
237         DPUTC(d, ch);
238         break;
239     }
240   }
241
242 end:
243   DPUTZ(d);
244   return (0);
245
246 #undef f_brace
247 }
248
249 enum { TVCODE_BARE, TVCODE_HEX, TVCODE_BASE64, TVCODE_BASE32 };
250
251 static int collect_bare(dstr *d, struct tvec_state *tv)
252 {
253   size_t pos = d->len;
254   enum { WORD, SPACE, ESCAPE }; unsigned s = WORD;
255   int ch, rc;
256
257   for (;;) {
258     ch = getc(tv->fp);
259     switch (ch) {
260       case EOF:
261         tvec_syntax(tv, ch, "bareword");
262         rc = -1; goto end;
263       case '\n':
264         if (s == ESCAPE) { tv->lno++; goto addch; }
265         if (s == WORD) pos = d->len;
266         ungetc(ch, tv->fp); if (tvec_nexttoken(tv)) { rc = -1; goto end; }
267         DPUTC(d, ' '); s = SPACE;
268         break;
269       case '"': case '\'': case '!':
270         if (s == SPACE) { ungetc(ch, tv->fp); goto done; }
271         goto addch;
272       case '\\':
273         s = ESCAPE;
274         break;
275       default:
276         if (s != ESCAPE && isspace(ch)) {
277           if (s == WORD) pos = d->len;
278           DPUTC(d, ch); s = SPACE;
279           break;
280         }
281       addch:
282         DPUTC(d, ch); s = WORD;
283     }
284   }
285
286 done:
287   if (s == SPACE) d->len = pos;
288   DPUTZ(d); rc = 0;
289 end:
290   return (rc);
291 }
292
293 static void set_up_encoding(const codec_class **ccl_out, unsigned *f_out,
294                             unsigned code)
295 {
296   switch (code) {
297     case TVCODE_BARE:
298       *ccl_out = 0; *f_out = 0;
299       break;
300     case TVCODE_HEX:
301       *ccl_out = &hex_class; *f_out = CDCF_IGNCASE;
302       break;
303     case TVCODE_BASE32:
304       *ccl_out = &base32_class; *f_out = CDCF_IGNCASE | CDCF_IGNEQPAD;
305       break;
306     case TVCODE_BASE64:
307       *ccl_out = &base64_class; *f_out = CDCF_IGNEQPAD;
308       break;
309     default:
310       abort();
311   }
312 }
313
314 static int read_compound_string(void **p_inout, size_t *sz_inout,
315                                 unsigned code, struct tvec_state *tv)
316 {
317   const codec_class *ccl; unsigned f;
318   codec *cdc;
319   dstr d = DSTR_INIT, w = DSTR_INIT;
320   char *p;
321   int ch, err, rc;
322
323   set_up_encoding(&ccl, &f, code);
324   if (tvec_nexttoken(tv)) tvec_syntax(tv, fgetc(tv->fp), "string");
325   do {
326     ch = getc(tv->fp);
327     if (ch == '"' || ch == '\'')
328       { if (read_quoted_string(&d, ch, tv)) { rc = -1; goto end; } }
329     else if (ch == '!') {
330       ungetc(ch, tv->fp);
331       DRESET(&w); tvec_readword(tv, &w, ";", "`!'-keyword");
332       if (STRCMP(w.buf, ==, "!bare")) code = TVCODE_BARE;
333       else if (STRCMP(w.buf, ==, "!hex")) code = TVCODE_HEX;
334       else if (STRCMP(w.buf, ==, "!base32")) code = TVCODE_BASE32;
335       else if (STRCMP(w.buf, ==, "!base64")) code = TVCODE_BASE64;
336       else {
337         tvec_error(tv, "unknown string keyword `%s'", w.buf);
338         rc = -1; goto end;
339       }
340       set_up_encoding(&ccl, &f, code);
341     } else if (ccl) {
342       ungetc(ch, tv->fp);
343       DRESET(&w);
344       if (tvec_readword(tv, &w, ";", "%s-encoded fragment", ccl->name))
345         { rc = -1; goto end; }
346       cdc = ccl->decoder(f);
347       err = cdc->ops->code(cdc, w.buf, w.len, &d);
348       if (!err) err = cdc->ops->code(cdc, 0, 0, &d);
349       if (err) {
350         tvec_error(tv, "invalid %s fragment `%s': %s",
351                    ccl->name, w.buf, codec_strerror(err));
352         rc = -1; goto end;
353       }
354       cdc->ops->destroy(cdc);
355     } else switch (code) {
356       case TVCODE_BARE:
357         ungetc(ch, tv->fp);
358         if (collect_bare(&d, tv)) goto done;
359         break;
360       default:
361         abort();
362     }
363   } while (!tvec_nexttoken(tv));
364
365 done:
366   if (*sz_inout <= d.len)
367     { xfree(*p_inout); *p_inout = xmalloc(d.len + 1); }
368   p = *p_inout; memcpy(p, d.buf, d.len); p[d.len] = 0; *sz_inout = d.len;
369   rc = 0;
370 end:
371   dstr_destroy(&d); dstr_destroy(&w);
372   return (rc);
373 }
374
375 /*----- Skeleton ----------------------------------------------------------*/
376 /*
377 static void init_...(union tvec_regval *rv, const struct tvec_regdef *rd)
378 static void release_...(union tvec_regval *rv, const struct tvec_regdef *rd)
379 static int eq_...(const union tvec_regval *rv0, const union tvec_regval *rv1,
380                   const struct tvec_regdef *rd)
381 static size_t measure_...(const union tvec_regval *rv,
382                           const struct tvec_regdef *rd)
383 static int tobuf_...(buf *b, const union tvec_regval *rv,
384                      const struct tvec_regdef *rd)
385 static int frombuf_...(buf *b, union tvec_regval *rv,
386                        const struct tvec_regdef *rd)
387 static void parse_...(union tvec_regval *rv, const struct tvec_regdef *rd,
388                       struct tvec_state *tv)
389 static void dump_...(const union tvec_regval *rv,
390                      const struct tvec_regdef *rd,
391                      struct tvec_state *tv, unsigned style)
392
393 const struct tvec_regty tvty_... = {
394   init_..., release_..., eq_..., measure_...,
395   tobuf_..., frombuf_...,
396   parse_..., dump_...
397 };
398 */
399 /*----- Signed and unsigned integer types ---------------------------------*/
400
401 static void init_int(union tvec_regval *rv, const struct tvec_regdef *rd)
402   { rv->i = 0; }
403
404 static void init_uint(union tvec_regval *rv, const struct tvec_regdef *rd)
405   { rv->u = 0; }
406
407 static void release_int(union tvec_regval *rv, const struct tvec_regdef *rd)
408   { ; }
409
410 static int eq_int(const union tvec_regval *rv0, const union tvec_regval *rv1,
411                   const struct tvec_regdef *rd)
412   { return (rv0->i == rv1->i); }
413
414 static int eq_uint(const union tvec_regval *rv0,
415                    const union tvec_regval *rv1,
416                    const struct tvec_regdef *rd)
417   { return (rv0->u == rv1->u); }
418
419 static size_t measure_int(const union tvec_regval *rv,
420                           const struct tvec_regdef *rd)
421   { return (8); }
422
423 static int tobuf_int(buf *b, const union tvec_regval *rv,
424                      const struct tvec_regdef *rd)
425   { return (signed_to_buf(b, rv->i)); }
426
427 static int tobuf_uint(buf *b, const union tvec_regval *rv,
428                        const struct tvec_regdef *rd)
429   { return (unsigned_to_buf(b, rv->u)); }
430
431 static int frombuf_int(buf *b, union tvec_regval *rv,
432                        const struct tvec_regdef *rd)
433   { return (signed_from_buf(b, &rv->i)); }
434
435 static int frombuf_uint(buf *b, union tvec_regval *rv,
436                         const struct tvec_regdef *rd)
437   { return (unsigned_from_buf(b, &rv->u)); }
438
439 static int parse_int(union tvec_regval *rv, const struct tvec_regdef *rd,
440                      struct tvec_state *tv)
441 {
442   dstr d = DSTR_INIT;
443   int rc;
444
445   if (tvec_readword(tv, &d, ";", "signed integer")) { rc = -1; goto end; }
446   if (parse_signed(&rv->i, d.buf, rd->arg.p, tv)) { rc = -1; goto end; }
447   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
448   rc = 0;
449 end:
450   dstr_destroy(&d);
451   return (rc);
452 }
453
454 static int parse_uint(union tvec_regval *rv, const struct tvec_regdef *rd,
455                       struct tvec_state *tv)
456 {
457   dstr d = DSTR_INIT;
458   int rc;
459
460   if (tvec_readword(tv, &d, ";", "unsigned integer")) { rc = -1; goto end; }
461   if (parse_unsigned(&rv->u, d.buf, rd->arg.p, tv)) { rc = -1; goto end; }
462   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
463   rc = 0;
464 end:
465   dstr_destroy(&d);
466   return (rc);
467 }
468
469 static void dump_int(const union tvec_regval *rv,
470                      const struct tvec_regdef *rd,
471                      struct tvec_state *tv, unsigned style)
472 {
473   unsigned long u;
474
475   tvec_write(tv, "%ld", rv->i);
476   if (!(style&TVSF_COMPACT)) {
477     if (rv->i >= 0) u = rv->i;
478     else u = -(unsigned long)rv->i;
479     tvec_write(tv, " ; = %s0x%0*lx", rv->i < 0 ? "-" : "", hex_width(u), u);
480   }
481 }
482
483 static void dump_uint(const union tvec_regval *rv,
484                       const struct tvec_regdef *rd,
485                       struct tvec_state *tv, unsigned style)
486 {
487   tvec_write(tv, "%lu", rv->u);
488   if (!(style&TVSF_COMPACT))
489     tvec_write(tv, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
490 }
491
492 const struct tvec_regty tvty_int = {
493   init_int, release_int, eq_int, measure_int,
494   tobuf_int, frombuf_int,
495   parse_int, dump_int
496 };
497
498 const struct tvec_irange
499   tvrange_schar = { SCHAR_MIN, SCHAR_MAX },
500   tvrange_short = { SHRT_MIN, SHRT_MAX },
501   tvrange_int = { INT_MIN, INT_MAX },
502   tvrange_long = { LONG_MIN, LONG_MAX },
503   tvrange_sbyte = { -128, 127 },
504   tvrange_i16 = { -32768, +32767 },
505   tvrange_i32 = { -2147483648, 2147483647 };
506
507 const struct tvec_regty tvty_uint = {
508   init_uint, release_int, eq_uint, measure_int,
509   tobuf_uint, frombuf_uint,
510   parse_uint, dump_uint
511 };
512
513 const struct tvec_urange
514   tvrange_uchar = { 0, UCHAR_MAX },
515   tvrange_ushort = { 0, USHRT_MAX },
516   tvrange_uint = { 0, UINT_MAX },
517   tvrange_ulong = { 0, ULONG_MAX },
518   tvrange_size = { 0, (size_t)-1 },
519   tvrange_byte = { 0, 255 },
520   tvrange_u16 = { 0, 65535 },
521   tvrange_u32 = { 0, 4294967296 };
522
523 int tvec_claimeq_int(struct tvec_state *tv, long i0, long i1,
524                      const char *file, unsigned lno, const char *expr)
525 {
526   tv->in[0].v.i = i0; tv->out[0].v.i = i1;
527   return (tvec_claimeq(tv, &tvty_int, 0, file, lno, expr));
528 }
529
530 int tvec_claimeq_uint(struct tvec_state *tv,
531                       unsigned long u0, unsigned long u1,
532                       const char *file, unsigned lno, const char *expr)
533 {
534   tv->in[0].v.u = u0; tv->out[0].v.u = u1;
535   return (tvec_claimeq(tv, &tvty_uint, 0, file, lno, expr));
536 }
537
538 /*----- Enumerations ------------------------------------------------------*/
539
540 static void init_enum(union tvec_regval *rv, const struct tvec_regdef *rd)
541 {
542   const struct tvec_enuminfo *ei = rd->arg.p;
543
544   switch (ei->mv) {
545 #define CASE(tag, ty, slot)                                             \
546         case TVMISC_##tag: rv->slot = 0; break;
547     TVEC_MISCSLOTS(CASE)
548 #undef CASE
549     default: abort();
550   }
551 }
552
553 static int eq_enum(const union tvec_regval *rv0,
554                    const union tvec_regval *rv1,
555                    const struct tvec_regdef *rd)
556 {
557   const struct tvec_enuminfo *ei = rd->arg.p;
558
559   switch (ei->mv) {
560 #define CASE(tag, ty, slot)                                             \
561         case TVMISC_##tag: return (rv0->slot == rv1->slot);
562     TVEC_MISCSLOTS(CASE)
563 #undef CASE
564     default: abort();
565   }
566 }
567
568 static int tobuf_enum(buf *b, const union tvec_regval *rv,
569                       const struct tvec_regdef *rd)
570 {
571   const struct tvec_enuminfo *ei = rd->arg.p;
572
573   switch (ei->mv) {
574 #define CASE(tag, ty, slot)                                             \
575         case TVMISC_##tag: return (HANDLE_##tag);
576 #define HANDLE_INT      signed_to_buf(b, rv->i)
577 #define HANDLE_UINT     unsigned_to_buf(b, rv->u)
578 #define HANDLE_PTR      -1
579     TVEC_MISCSLOTS(CASE)
580 #undef CASE
581 #undef HANDLE_INT
582 #undef HANDLE_UINT
583 #undef HANDLE_PTR
584     default: abort();
585   }
586   return (0);
587 }
588
589 static int frombuf_enum(buf *b, union tvec_regval *rv,
590                         const struct tvec_regdef *rd)
591 {
592   const struct tvec_enuminfo *ei = rd->arg.p;
593
594   switch (ei->mv) {
595 #define CASE(tag, ty, slot)                                             \
596         case TVMISC_##tag: return (HANDLE_##tag);
597 #define HANDLE_INT      signed_from_buf(b, &rv->i)
598 #define HANDLE_UINT     unsigned_from_buf(b, &rv->u)
599 #define HANDLE_PTR      -1
600     TVEC_MISCSLOTS(CASE)
601 #undef CASE
602 #undef HANDLE_INT
603 #undef HANDLE_UINT
604 #undef HANDLE_PTR
605     default: abort();
606   }
607 }
608
609 static int parse_enum(union tvec_regval *rv, const struct tvec_regdef *rd,
610                       struct tvec_state *tv)
611 {
612   const struct tvec_enuminfo *ei = rd->arg.p;
613 #define DECLS(tag, ty, slot)                                            \
614         const struct tvec_##slot##assoc *slot##a;
615   TVEC_MISCSLOTS(DECLS)
616 #undef DECLS
617   dstr d = DSTR_INIT;
618   int rc;
619
620   if (tvec_readword(tv, &d, ";", "enumeration tag or literal integer"))
621     { rc = -1; goto end; }
622   switch (ei->mv) {
623 #define CASE(tag_, ty, slot)                                            \
624         case TVMISC_##tag_:                                             \
625           for (slot##a = ei->u.slot.av; slot##a->tag; slot##a++)        \
626             if (STRCMP(d.buf, ==, slot##a->tag))                        \
627               { rv->slot = FETCH_##tag_; goto done; }
628 #define FETCH_INT (ia->i)
629 #define FETCH_UINT (ua->u)
630 #define FETCH_PTR ((/*unconst*/ void *)(pa->p))
631     TVEC_MISCSLOTS(CASE)
632 #undef CASE
633 #undef FETCH_INT
634 #undef FETCH_UINT
635 #undef FETCH_PTR
636   }
637
638   switch (ei->mv) {
639 #define CASE(tag, ty, slot)                                             \
640         case TVMISC_##tag: HANDLE_##tag goto done;
641 #define HANDLE_INT                                                      \
642         if (parse_signed(&rv->i, d.buf, ei->u.i.ir, tv))                \
643           { rc = -1; goto end; }
644 #define HANDLE_UINT                                                     \
645         if (parse_unsigned(&rv->u, d.buf, ei->u.u.ur, tv))              \
646           { rc = -1; goto end; }
647 #define HANDLE_PTR                                                      \
648         if (STRCMP(d.buf, ==, "#nil")) rv->p = 0;                       \
649         else goto tagonly;
650     TVEC_MISCSLOTS(CASE)
651 #undef CASE
652 #undef HANDLE_INT
653 #undef HANDLE_UINT
654 #undef HANDLE_PTR
655     default: tagonly:
656       tvec_error(tv, "unknown `%s' value `%s'", ei->name, d.buf);
657       rc = -1; goto end;
658   }
659
660 done:
661   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
662   rc = 0;
663 end:
664   dstr_destroy(&d);
665   return (rc);
666 }
667
668 static void dump_enum(const union tvec_regval *rv,
669                       const struct tvec_regdef *rd,
670                       struct tvec_state *tv, unsigned style)
671 {
672   const struct tvec_enuminfo *ei = rd->arg.p;
673 #define DECLS(tag, ty, slot)                                            \
674         const struct tvec_##slot##assoc *slot##a;
675   TVEC_MISCSLOTS(DECLS)
676 #undef DECLS
677   const char *tag;
678   unsigned long u;
679   unsigned f = 0;
680 #define f_known 1u
681
682   switch (ei->mv) {
683 #define CASE(tag_, ty, slot)                                            \
684         case TVMISC_##tag_:                                             \
685           for (slot##a = ei->u.slot.av; slot##a->tag; slot##a++)        \
686             if (rv->slot == slot##a->slot)                              \
687               { tag = slot##a->tag; goto found; }                       \
688           break;
689     TVEC_MISCSLOTS(CASE)
690 #undef CASE
691     default: abort();
692   }
693   goto print_int;
694
695 found:
696   f |= f_known;
697   tvec_write(tv, "%s", tag);
698   if (style&TVSF_COMPACT) return;
699   tvec_write(tv, " ; = ");
700
701 print_int:
702   switch (ei->mv) {
703 #define CASE(tag, ty, slot)                                             \
704         case TVMISC_##tag: HANDLE_##tag break;
705 #define HANDLE_INT      tvec_write(tv, "%ld", rv->i);
706 #define HANDLE_UINT     tvec_write(tv, "%lu", rv->u);
707 #define HANDLE_PTR      if (!rv->p) tvec_write(tv, "#nil");             \
708                         else tvec_write(tv, "#<%s %p>", ei->name, rv->p);
709     TVEC_MISCSLOTS(CASE)
710 #undef CASE
711 #undef HANDLE_INT
712 #undef HANDLE_UINT
713 #undef HANDLE_PTR
714   }
715
716   switch (ei->mv) {
717     case TVMISC_INT:
718       if (!(f&f_known)) tvec_write(tv, " ;");
719       if (rv->i >= 0) u = rv->i;
720       else u = -(unsigned long)rv->i;
721       tvec_write(tv, " = %s0x%0*lx", rv->i < 0 ? "-" : "", hex_width(u), u);
722       break;
723     case TVMISC_UINT:
724       if (!(f&f_known)) tvec_write(tv, " ;");
725       tvec_write(tv, " = 0x%0*lx", hex_width(rv->u), rv->u);
726       break;
727   }
728 }
729
730 const struct tvec_regty tvty_enum = {
731   init_enum, release_int, eq_enum, measure_int,
732   tobuf_enum, frombuf_enum,
733   parse_enum, dump_enum
734 };
735
736 #define DEFCLAIM(tag, ty, slot)                                         \
737         int tvec_claimeq_##slot##enum(struct tvec_state *tv,            \
738                                       const struct tvec_enuminfo *ei,   \
739                                       ty e0, ty e1,                     \
740                                       const char *file, unsigned lno,   \
741                                       const char *expr)                 \
742         {                                                               \
743           union tvec_misc arg;                                          \
744                                                                         \
745           assert(ei->mv == TVMISC_##tag);                               \
746           arg.p = ei;                                                   \
747           tv->in[0].v.slot = GET_##tag(e0);                             \
748           tv->out[0].v.slot = GET_##tag(e1);                            \
749           return (tvec_claimeq(tv, &tvty_enum, &arg, file, lno, expr)); \
750         }
751 #define GET_INT(e) (e)
752 #define GET_UINT(e) (e)
753 #define GET_PTR(e) ((/*unconst*/ void *)(e))
754 TVEC_MISCSLOTS(DEFCLAIM)
755 #undef DEFCLAIM
756 #undef GET_INT
757 #undef GET_UINT
758 #undef GET_PTR
759
760 /*----- Flag types --------------------------------------------------------*/
761
762 static int parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd,
763                        struct tvec_state *tv)
764 {
765   const struct tvec_flaginfo *fi = rd->arg.p;
766   const struct tvec_flag *f;
767   unsigned long m = 0, v = 0, t;
768   dstr d = DSTR_INIT;
769   int ch, rc;
770
771   for (;;) {
772     DRESET(&d);
773     if (tvec_readword(tv, &d, "|;", "flag name or integer"))
774       { rc = -1; goto end; }
775
776     for (f = fi->fv; f->tag; f++)
777       if (STRCMP(f->tag, ==, d.buf)) {
778         if (m&f->m)
779           { tvec_error(tv, "colliding flag setting"); rc = -1; goto end; }
780         else
781           { m |= f->m; v |= f->v; goto next; }
782       }
783
784     if (parse_unsigned(&t, d.buf, fi->range, tv)) { rc = -1; goto end; }
785     v |= t;
786   next:
787     if (tvec_nexttoken(tv)) break;
788     ch = getc(tv->fp);
789       if (ch != '|') { tvec_syntax(tv, ch, "`|'"); rc = -1; goto end; }
790     if (tvec_nexttoken(tv))
791       { tvec_syntax(tv, '\n', "flag name or integer"); rc = -1; goto end; }
792   }
793   rv->u = v;
794   rc = 0;
795 end:
796   dstr_destroy(&d);
797   return (rc);
798 }
799
800 static void dump_flags(const union tvec_regval *rv,
801                        const struct tvec_regdef *rd,
802                        struct tvec_state *tv, unsigned style)
803 {
804   const struct tvec_flaginfo *fi = rd->arg.p;
805   const struct tvec_flag *f;
806   unsigned long m = ~(unsigned long)0, v = rv->u;
807   const char *sep;
808
809   for (f = fi->fv, sep = ""; f->tag; f++)
810     if ((m&f->m) && (v&f->m) == f->v) {
811       tvec_write(tv, "%s%s", sep, f->tag); m &= ~f->m;
812       sep = style&TVSF_COMPACT ? "|" : " | ";
813     }
814
815   if (v&m) tvec_write(tv, "%s0x%0*lx", sep, hex_width(v), v&m);
816
817   if (!(style&TVSF_COMPACT))
818     tvec_write(tv, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
819 }
820
821 const struct tvec_regty tvty_flags = {
822   init_uint, release_int, eq_uint, measure_int,
823   tobuf_uint, frombuf_uint,
824   parse_flags, dump_flags
825 };
826
827 int tvec_claimeq_flags(struct tvec_state *tv,
828                        const struct tvec_flaginfo *fi,
829                        unsigned long f0, unsigned long f1,
830                        const char *file, unsigned lno, const char *expr)
831 {
832   union tvec_misc arg;
833
834   arg.p = fi; tv->in[0].v.u = f0; tv->out[0].v.u = f1;
835   return (tvec_claimeq(tv, &tvty_flags, &arg, file, lno, expr));
836 }
837
838 /*----- Text and byte strings ---------------------------------------------*/
839
840 void tvec_allocstring(union tvec_regval *rv, size_t sz)
841 {
842   if (rv->str.sz < sz) { xfree(rv->str.p); rv->str.p = xmalloc(sz); }
843   rv->str.sz = sz;
844 }
845
846 void tvec_allocbytes(union tvec_regval *rv, size_t sz)
847 {
848   if (rv->bytes.sz < sz) { xfree(rv->bytes.p); rv->bytes.p = xmalloc(sz); }
849   rv->bytes.sz = sz;
850 }
851
852 static void init_string(union tvec_regval *rv, const struct tvec_regdef *rd)
853   { rv->str.p = 0; rv->str.sz = 0; }
854
855 static void init_bytes(union tvec_regval *rv, const struct tvec_regdef *rd)
856   { rv->bytes.p = 0; rv->bytes.sz = 0; }
857
858 static void release_string(union tvec_regval *rv,
859                           const struct tvec_regdef *rd)
860   { xfree(rv->str.p); }
861
862 static void release_bytes(union tvec_regval *rv,
863                           const struct tvec_regdef *rd)
864   { xfree(rv->bytes.p); }
865
866 static int eq_string(const union tvec_regval *rv0,
867                      const union tvec_regval *rv1,
868                      const struct tvec_regdef *rd)
869 {
870   return (rv0->str.sz == rv1->str.sz &&
871           (!rv0->bytes.sz ||
872            MEMCMP(rv0->str.p, ==, rv1->str.p, rv1->str.sz)));
873 }
874
875 static int eq_bytes(const union tvec_regval *rv0,
876                     const union tvec_regval *rv1,
877                     const struct tvec_regdef *rd)
878 {
879   return (rv0->bytes.sz == rv1->bytes.sz &&
880           (!rv0->bytes.sz ||
881            MEMCMP(rv0->bytes.p, ==, rv1->bytes.p, rv1->bytes.sz)));
882 }
883
884 static size_t measure_string(const union tvec_regval *rv,
885                              const struct tvec_regdef *rd)
886   { return (rv->str.sz + 4); }
887
888 static size_t measure_bytes(const union tvec_regval *rv,
889                             const struct tvec_regdef *rd)
890   { return (rv->bytes.sz + 4); }
891
892 static int tobuf_string(buf *b, const union tvec_regval *rv,
893                         const struct tvec_regdef *rd)
894   { return (buf_putmem32l(b, rv->str.p, rv->str.sz)); }
895
896 static int tobuf_bytes(buf *b, const union tvec_regval *rv,
897                        const struct tvec_regdef *rd)
898   { return (buf_putmem32l(b, rv->bytes.p, rv->bytes.sz)); }
899
900 static int frombuf_string(buf *b, union tvec_regval *rv,
901                           const struct tvec_regdef *rd)
902 {
903   const void *p;
904   size_t sz;
905
906   p = buf_getmem32l(b, &sz); if (!p) return (-1);
907   tvec_allocstring(rv, sz); memcpy(rv->str.p, p, sz);
908   return (0);
909 }
910
911 static int frombuf_bytes(buf *b, union tvec_regval *rv,
912                          const struct tvec_regdef *rd)
913 {
914   const void *p;
915   size_t sz;
916
917   p = buf_getmem32l(b, &sz); if (!p) return (-1);
918   tvec_allocbytes(rv, sz); memcpy(rv->bytes.p, p, sz);
919   return (0);
920 }
921
922 static int check_string_length(size_t sz, const struct tvec_urange *ur,
923                                struct tvec_state *tv)
924 {
925   if (ur && (ur->min > sz || sz > ur->max))
926     return (tvec_error(tv,
927                        "invalid string length %lu; must be in [%lu..%lu]",
928                        (unsigned long)sz, ur->min, ur->max));
929   return (0);
930 }
931
932 static int parse_string(union tvec_regval *rv, const struct tvec_regdef *rd,
933                         struct tvec_state *tv)
934 {
935   void *p = rv->str.p;
936
937   if (read_compound_string(&p, &rv->str.sz, TVCODE_BARE, tv)) return (-1);
938   rv->str.p = p;
939   if (check_string_length(rv->str.sz, rd->arg.p, tv)) return (-1);
940   return (0);
941 }
942
943 static int parse_bytes(union tvec_regval *rv, const struct tvec_regdef *rd,
944                        struct tvec_state *tv)
945 {
946   void *p = rv->bytes.p;
947
948   if (read_compound_string(&p, &rv->bytes.sz, TVCODE_HEX, tv)) return (-1);
949   rv->bytes.p = p;
950   if (check_string_length(rv->bytes.sz, rd->arg.p, tv)) return (-1);
951   return (0);
952 }
953
954 static void dump_string(const union tvec_regval *rv,
955                         const struct tvec_regdef *rd,
956                         struct tvec_state *tv, unsigned style)
957 {
958   const unsigned char *p, *q, *l;
959   int ch;
960   unsigned f = 0;
961 #define f_nonword 1u
962 #define f_newline 2u
963
964   if (!rv->str.sz) { tvec_write(tv, "\"\""); return; }
965
966   p = (const unsigned char *)rv->str.p; l = p + rv->str.sz;
967   if (*p == '!' || *p == ';' || *p == '"' || *p == '\'') goto quote;
968   for (q = p; q < l; q++)
969     if (*q == '\n' && q != l - 1) f |= f_newline;
970     else if (!*q || !isgraph(*q) || *q == '\\') f |= f_nonword;
971   if (f&f_newline) { tvec_write(tv, "\n\t"); goto quote; }
972   else if (f&f_nonword) goto quote;
973   tv->output->ops->write(tv->output, (const char *)p, rv->str.sz); return;
974
975 quote:
976   tvec_write(tv, "\"");
977   for (q = p; q < l; q++)
978     switch (*q) {
979       case '"': case '\\': ch = *q; goto escape;
980       case '\a': ch = 'a'; goto escape;
981       case '\b': ch = 'b'; goto escape;
982       case '\x1b': ch = 'e'; goto escape;
983       case '\f': ch = 'f'; goto escape;
984       case '\r': ch = 'r'; goto escape;
985       case '\t': ch = 't'; goto escape;
986       case '\v': ch = 'v'; goto escape;
987       escape:
988         if (p < q)
989           tv->output->ops->write(tv->output, (const char *)p, q - p);
990         tvec_write(tv, "\\%c", ch); p = q + 1;
991         break;
992
993       case '\n':
994         if (p < q)
995           tv->output->ops->write(tv->output, (const char *)p, q - p);
996         tvec_write(tv, "\\n"); p = q + 1;
997         if (!(style&TVSF_COMPACT) && q < l) tvec_write(tv, "\"\t\"");
998         break;
999
1000       default:
1001         if (isprint(*q)) break;
1002         if (p < q)
1003           tv->output->ops->write(tv->output, (const char *)p, q - p);
1004         tvec_write(tv, "\\x{%0*x}", hex_width(UCHAR_MAX), *q); p = q + 1;
1005         break;
1006     }
1007   if (p < q) tv->output->ops->write(tv->output, (const char *)p, q - p);
1008   tvec_write(tv, "\"");
1009
1010 #undef f_nonword
1011 #undef f_newline
1012 }
1013
1014 static void dump_bytes(const union tvec_regval *rv,
1015                        const struct tvec_regdef *rd,
1016                        struct tvec_state *tv, unsigned style)
1017 {
1018   const unsigned char *p = rv->bytes.p, *l = p + rv->bytes.sz;
1019   size_t off, sz = rv->bytes.sz;
1020   unsigned i, n;
1021   int wd;
1022
1023   if (!sz) {
1024     tvec_write(tv, style&TVSF_COMPACT ? "\"\"" : "\"\" ; empty");
1025     return;
1026   }
1027
1028   if (style&TVSF_COMPACT) {
1029     while (p < l) tvec_write(tv, "%02x", *p++);
1030     return;
1031   }
1032
1033   if (sz > 16) tvec_write(tv, "\n\t");
1034
1035   off = 0; wd = hex_width(sz);
1036   while (p < l) {
1037     if (l - p < 16) n = l - p;
1038     else n = 16;
1039
1040     for (i = 0; i < 16; i++) {
1041       if (i < n) tvec_write(tv, "%02x", p[i]);
1042       else tvec_write(tv, "  ");
1043       if (i%4 == 3) tvec_write(tv, " ");
1044     }
1045     tvec_write(tv, " ; ");
1046     if (sz > 16) tvec_write(tv, "[%0*lx] ", wd, (unsigned long)off);
1047     for (i = 0; i < n; i++)
1048       tvec_write(tv, "%c", isprint(p[i]) ? p[i] : '.');
1049     p += n; off += n;
1050     if (p < l) tvec_write(tv, "\n\t");
1051   }
1052 }
1053
1054 const struct tvec_regty tvty_string = {
1055   init_string, release_string, eq_string, measure_string,
1056   tobuf_string, frombuf_string,
1057   parse_string, dump_string
1058 };
1059
1060 const struct tvec_regty tvty_bytes = {
1061   init_bytes, release_bytes, eq_bytes, measure_bytes,
1062   tobuf_bytes, frombuf_bytes,
1063   parse_bytes, dump_bytes
1064 };
1065
1066 int tvec_claimeq_string(struct tvec_state *tv,
1067                         const char *p0, size_t sz0,
1068                         const char *p1, size_t sz1,
1069                         const char *file, unsigned lno, const char *expr)
1070 {
1071   tv->in[0].v.str.p = (/*unconst*/ char *)p0; tv->in[0].v.str.sz = sz0;
1072   tv->out[0].v.str.p =(/*unconst*/ char *) p1; tv->out[0].v.str.sz = sz1;
1073   return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
1074 }
1075
1076 int tvec_claimeq_strz(struct tvec_state *tv,
1077                       const char *p0, const char *p1,
1078                       const char *file, unsigned lno, const char *expr)
1079 {
1080   tv->in[0].v.str.p = (/*unconst*/ char *)p0;
1081     tv->in[0].v.str.sz = strlen(p0);
1082   tv->out[0].v.str.p = (/*unconst*/ char *)p1;
1083     tv->out[0].v.str.sz = strlen(p1);
1084   return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
1085 }
1086
1087 int tvec_claimeq_bytes(struct tvec_state *tv,
1088                        const void *p0, size_t sz0,
1089                        const void *p1, size_t sz1,
1090                        const char *file, unsigned lno, const char *expr)
1091 {
1092   tv->in[0].v.bytes.p = (/*unconst*/ void *)p0;
1093     tv->in[0].v.bytes.sz = sz0;
1094   tv->out[0].v.bytes.p = (/*unconst*/ void *)p1;
1095     tv->out[0].v.bytes.sz = sz1;
1096   return (tvec_claimeq(tv, &tvty_bytes, 0, file, lno, expr));
1097 }
1098
1099 /*----- Buffer type -------------------------------------------------------*/
1100
1101 static int eq_buffer(const union tvec_regval *rv0,
1102                      const union tvec_regval *rv1,
1103                      const struct tvec_regdef *rd)
1104   { return (rv0->bytes.sz == rv1->bytes.sz); }
1105
1106 static int tobuf_buffer(buf *b, const union tvec_regval *rv,
1107                          const struct tvec_regdef *rd)
1108   { return (unsigned_to_buf(b, rv->bytes.sz)); }
1109
1110 static int frombuf_buffer(buf *b, union tvec_regval *rv,
1111                           const struct tvec_regdef *rd)
1112 {
1113   unsigned long u;
1114
1115   if (unsigned_from_buf(b, &u)) return (-1);
1116   if (u > (size_t)-1) return (-1);
1117   tvec_allocbytes(rv, u); memset(rv->bytes.p, '!', u);
1118   return (0);
1119 }
1120
1121 static const char units[] = "kMGTPEZY";
1122
1123 static int parse_buffer(union tvec_regval *rv,
1124                         const struct tvec_regdef *rd,
1125                         struct tvec_state *tv)
1126 {
1127   dstr d = DSTR_INIT;
1128   char *q; const char *unit;
1129   int olderr;
1130   size_t pos;
1131   unsigned long u, t;
1132   int rc;
1133   unsigned f = 0;
1134 #define f_range 1u
1135
1136   if (tvec_readword(tv, &d, ";", "buffer length")) { rc = -1; goto end; }
1137   olderr = errno; errno = 0;
1138   u = strtoul(d.buf, &q, 0);
1139   if (errno) goto bad;
1140   errno = olderr;
1141   if (!*q) {
1142     tvec_skipspc(tv); pos = d.len;
1143     if (!tvec_readword(tv, &d, ";", 0)) pos++;
1144     q = d.buf + pos;
1145   }
1146
1147   if (u > (size_t)-1) goto rangerr;
1148   for (t = u, unit = units; *unit; unit++) {
1149     if (t > (size_t)-1/1024) f |= f_range;
1150     else t *= 1024;
1151     if (*q == *unit && (!q[1] || q[1] == 'B')) {
1152       if (f&f_range) goto rangerr;
1153       u = t; q += 2; break;
1154     }
1155   }
1156   if (*q && *q != ';') goto bad;
1157   if (check_string_length(u, rd->arg.p, tv)) { rc = -1; goto end; }
1158
1159   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
1160   tvec_allocbytes(rv, u); memset(rv->bytes.p, '?', u);
1161   rc = 0;
1162 end:
1163   DDESTROY(&d); return (rc);
1164
1165 bad:
1166   tvec_error(tv, "invalid buffer length `%s'", d.buf);
1167   rc = -1; goto end;
1168
1169 rangerr:
1170   tvec_error(tv, "buffer length `%s' out of range", d.buf);
1171   rc = -1; goto end;
1172
1173 #undef f_range
1174 }
1175
1176 static void dump_buffer(const union tvec_regval *rv,
1177                         const struct tvec_regdef *rd,
1178                         struct tvec_state *tv, unsigned style)
1179 {
1180   const char *unit;
1181   unsigned long u = rv->bytes.sz;
1182
1183   if (!u || u%1024)
1184     tvec_write(tv, "%lu B", u);
1185   else {
1186     for (unit = units, u /= 1024; !(u%1024) && unit[1]; u /= 1024, unit++);
1187     tvec_write(tv, "%lu %cB", u, *unit);
1188   }
1189 }
1190
1191 const struct tvec_regty tvty_buffer = {
1192   init_bytes, release_bytes, eq_buffer, measure_int,
1193   tobuf_buffer, frombuf_buffer,
1194   parse_buffer, dump_buffer
1195 };
1196
1197 /*----- That's all, folks -------------------------------------------------*/