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