+/* -*-c-*-
+ *
+ * Types for the test-vector framework
+ *
+ * (c) 2023 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib. If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "buf.h"
+#include "codec.h"
+# include "base32.h"
+# include "base64.h"
+# include "hex.h"
+#include "dstr.h"
+#include "tvec.h"
+
+/*----- Preliminary utilities ---------------------------------------------*/
+
+static int signed_to_buf(buf *b, long i)
+{
+ kludge64 k;
+ unsigned long u;
+
+ u = i;
+ if (i >= 0) ASSIGN64(k, u);
+ else { ASSIGN64(k, ~u); CPL64(k, k); }
+ return (buf_putk64l(b, k));
+}
+
+static int signed_from_buf(buf *b, long *i_out)
+{
+ kludge64 k, lmax, not_lmin;
+
+ ASSIGN64(lmax, LONG_MAX); ASSIGN64(not_lmin, ~(unsigned long)LONG_MIN);
+ if (buf_getk64l(b, &k)) return (-1);
+ if (CMP64(k, <=, lmax)) *i_out = (long)GET64(unsigned long, k);
+ else {
+ CPL64(k, k);
+ if (CMP64(k, <=, not_lmin)) *i_out = -(long)GET64(unsigned long, k) - 1;
+ else return (-1);
+ }
+ return (0);
+}
+
+static int unsigned_to_buf(buf *b, unsigned long u)
+ { kludge64 k; ASSIGN64(k, u); return (buf_putk64l(b, k)); }
+
+static int unsigned_from_buf(buf *b, unsigned long *u_out)
+{
+ kludge64 k, ulmax;
+
+ ASSIGN64(ulmax, ULONG_MAX);
+ if (buf_getk64l(b, &k)) return (-1);
+ if (CMP64(k, >, ulmax)) return (-1);
+ *u_out = GET64(unsigned long, k); return (0);
+}
+
+static int hex_width(unsigned long u)
+{
+ int wd;
+ unsigned long t;
+
+ for (t = u >> 4, wd = 4; t >>= wd, wd *= 2, t; );
+ return (wd/4);
+}
+
+static void check_signed_range(long i,
+ const struct tvec_irange *ir,
+ struct tvec_state *tv)
+{
+ if (ir && (ir->min > i || i > ir->max))
+ tvec_error(tv, "integer %ld out of range (must be in [%ld .. %ld])",
+ i, ir->min, ir->max);
+}
+
+static void check_unsigned_range(unsigned long u,
+ const struct tvec_urange *ur,
+ struct tvec_state *tv)
+{
+ if (ur && (ur->min > u || u > ur->max))
+ tvec_error(tv, "integer %lu out of range (must be in [%lu .. %lu])",
+ u, ur->min, ur->max);
+}
+
+static void parse_signed(long *i_out, const char *p,
+ const struct tvec_irange *ir,
+ struct tvec_state *tv)
+{
+ char *q; const char *pp;
+ int olderr;
+ long i;
+
+ olderr = errno; errno = 0;
+ pp = p; if (*pp == '-' || *pp == '+') pp++;
+ if (!ISDIGIT(*pp)) tvec_syntax(tv, *pp, "signed integer");
+ i = strtol(p, &q, 0);
+ if (*q && !ISSPACE(*q)) tvec_syntax(tv, *q, "end-of-line");
+ if (errno) tvec_error(tv, "invalid integer `%s'", p);
+ check_signed_range(i, ir, tv);
+ errno = olderr; *i_out = i;
+}
+
+static void parse_unsigned(unsigned long *u_out, const char *p,
+ const struct tvec_urange *ur,
+ struct tvec_state *tv)
+{
+ char *q;
+ int olderr;
+ unsigned long u;
+
+ olderr = errno; errno = 0;
+ if (!ISDIGIT(*p)) tvec_syntax(tv, *p, "unsigned integer");
+ u = strtoul(p, &q, 0);
+ if (*q && !ISSPACE(*q)) tvec_syntax(tv, *q, "end-of-line");
+ if (errno) tvec_error(tv, "invalid integer `%s'", p);
+ check_unsigned_range(u, ur, tv);
+ errno = olderr; *u_out = u;
+}
+
+static int convert_hex(char ch, int *v_out)
+{
+ if ('0' <= ch && ch <= '9') { *v_out = ch - '0'; return (0); }
+ else if ('a' <= ch && ch <= 'f') { *v_out = ch - 'a' + 10; return (0); }
+ else if ('A' <= ch && ch <= 'F') { *v_out = ch - 'A' + 10; return (0); }
+ else return (-1);
+}
+
+static void read_quoted_string(dstr *d, int quote, struct tvec_state *tv)
+{
+ char expect[4];
+ int ch, i, esc;
+ unsigned f = 0;
+#define f_brace 1u
+
+ sprintf(expect, "`%c'", quote);
+
+ for (;;) {
+ ch = getc(tv->fp);
+ reinsert:
+ switch (ch) {
+ case EOF: case '\n':
+ tvec_syntax(tv, ch, expect);
+
+ case '\\':
+ if (quote == '\'') goto ordinary;
+ ch = getc(tv->fp);
+ switch (ch) {
+ case EOF: tvec_syntax(tv, ch, expect);
+ case '\n': tv->lno++; break;
+ case '\'': DPUTC(d, '\''); break;
+ case '\\': DPUTC(d, '\\'); break;
+ case '"': DPUTC(d, '"'); break;
+ case 'a': DPUTC(d, '\a'); break;
+ case 'b': DPUTC(d, '\b'); break;
+ case 'e': DPUTC(d, '\x1b'); break;
+ case 'f': DPUTC(d, '\f'); break;
+ case 'n': DPUTC(d, '\n'); break;
+ case 'r': DPUTC(d, '\r'); break;
+ case 't': DPUTC(d, '\t'); break;
+ case 'v': DPUTC(d, '\v'); break;
+
+ case 'x':
+ ch = getc(tv->fp);
+ if (ch == '{') { f |= f_brace; ch = getc(tv->fp); }
+ else f &= ~f_brace;
+ if (convert_hex(ch, &esc)) tvec_syntax(tv, ch, "hex digit");
+ for (;;) {
+ ch = getc(tv->fp); if (convert_hex(ch, &i)) break;
+ esc = 8*esc + i;
+ if (esc > UCHAR_MAX)
+ tvec_error(tv, "character code %d out of range", esc);
+ }
+ DPUTC(d, esc);
+ if (!(f&f_brace)) goto reinsert;
+ else if (ch != '}') tvec_syntax(tv, ch, "`}'");
+ break;
+
+ default:
+ if ('0' <= ch && ch < '8') {
+ i = 1; esc = ch - '0';
+ for (;;) {
+ ch = getc(tv->fp);
+ if (i > 3 || '0' > ch || ch >= '8') break;
+ esc = 8*esc + ch - '0'; i++;
+ }
+ if (esc > UCHAR_MAX)
+ tvec_error(tv, "character code %d out of range", esc);
+ DPUTC(d, esc);
+ goto reinsert;
+ }
+ tvec_syntax(tv, ch, "string escape");
+ break;
+ }
+ break;
+
+ default:
+ if (ch == quote) goto end;
+ ordinary:
+ DPUTC(d, ch);
+ break;
+ }
+ }
+
+end:
+ DPUTZ(d);
+
+#undef f_brace
+}
+
+enum { TVCODE_BARE, TVCODE_HEX, TVCODE_BASE64, TVCODE_BASE32 };
+
+static int collect_bare(dstr *d, struct tvec_state *tv)
+{
+ size_t pos = d->len;
+ enum { WORD, SPACE, ESCAPE }; unsigned s = WORD;
+ int ch, rc;
+
+ for (;;) {
+ ch = getc(tv->fp);
+ switch (ch) {
+ case EOF:
+ goto bad;
+ case '\n':
+ if (s == ESCAPE) { tv->lno++; goto addch; }
+ if (s == WORD) pos = d->len;
+ ungetc(ch, tv->fp); if (tvec_nexttoken(tv)) { rc = -1; goto done; }
+ DPUTC(d, ' '); s = SPACE;
+ break;
+ case '"': case '\'': case '!':
+ if (s == SPACE) { ungetc(ch, tv->fp); rc = 0; goto done; }
+ goto addch;
+ case '\\':
+ s = ESCAPE;
+ break;
+ default:
+ if (s != ESCAPE && isspace(ch)) {
+ if (s == WORD) pos = d->len;
+ DPUTC(d, ch); s = SPACE;
+ break;
+ }
+ addch:
+ DPUTC(d, ch); s = WORD;
+ }
+ }
+
+done:
+ if (s == SPACE) d->len = pos;
+ DPUTZ(d); return (rc);
+
+bad:
+ tvec_syntax(tv, ch, "bareword");
+}
+
+static void set_up_encoding(const codec_class **ccl_out, unsigned *f_out,
+ unsigned code)
+{
+ switch (code) {
+ case TVCODE_BARE:
+ *ccl_out = 0; *f_out = 0;
+ break;
+ case TVCODE_HEX:
+ *ccl_out = &hex_class; *f_out = CDCF_IGNCASE;
+ break;
+ case TVCODE_BASE32:
+ *ccl_out = &base32_class; *f_out = CDCF_IGNCASE | CDCF_IGNEQPAD;
+ break;
+ case TVCODE_BASE64:
+ *ccl_out = &base64_class; *f_out = CDCF_IGNEQPAD;
+ break;
+ default:
+ abort();
+ }
+}
+
+static void read_compound_string(void **p_inout, size_t *sz_inout,
+ unsigned code, struct tvec_state *tv)
+{
+ const codec_class *ccl; unsigned f;
+ codec *cdc;
+ dstr d = DSTR_INIT, w = DSTR_INIT;
+ char *p;
+ int ch, err;
+
+ set_up_encoding(&ccl, &f, code);
+ if (tvec_nexttoken(tv)) tvec_syntax(tv, fgetc(tv->fp), "string");
+ do {
+ ch = getc(tv->fp);
+ if (ch == '"' || ch == '\'')
+ read_quoted_string(&d, ch, tv);
+ else if (ch == '!') {
+ ungetc(ch, tv->fp);
+ DRESET(&w); tvec_readword(tv, &w, ";", "`!'-keyword");
+ if (STRCMP(w.buf, ==, "!bare")) code = TVCODE_BARE;
+ else if (STRCMP(w.buf, ==, "!hex")) code = TVCODE_HEX;
+ else if (STRCMP(w.buf, ==, "!base32")) code = TVCODE_BASE32;
+ else if (STRCMP(w.buf, ==, "!base64")) code = TVCODE_BASE64;
+ else tvec_error(tv, "unknown string keyword `%s'", w.buf);
+ set_up_encoding(&ccl, &f, code);
+ } else if (ccl) {
+ ungetc(ch, tv->fp);
+ DRESET(&w);
+ tvec_readword(tv, &w, ";", "%s-encoded fragment", ccl->name);
+ cdc = ccl->decoder(f);
+ err = cdc->ops->code(cdc, w.buf, w.len, &d);
+ if (!err) err = cdc->ops->code(cdc, 0, 0, &d);
+ if (err)
+ tvec_error(tv, "invalid %s fragment `%s': %s",
+ ccl->name, w.buf, codec_strerror(err));
+ cdc->ops->destroy(cdc);
+ } else switch (code) {
+ case TVCODE_BARE:
+ ungetc(ch, tv->fp);
+ if (collect_bare(&d, tv)) goto done;
+ break;
+ default:
+ abort();
+ }
+ } while (!tvec_nexttoken(tv));
+
+done:
+ if (*sz_inout <= d.len)
+ { xfree(*p_inout); *p_inout = xmalloc(d.len + 1); }
+ p = *p_inout; memcpy(p, d.buf, d.len); p[d.len] = 0; *sz_inout = d.len;
+ dstr_destroy(&d); dstr_destroy(&w);
+}
+
+/*----- Skeleton ----------------------------------------------------------*/
+/*
+static void init_...(union tvec_regval *rv, const struct tvec_regdef *rd)
+static void release_...(union tvec_regval *rv, const struct tvec_regdef *rd)
+static int eq_...(const union tvec_regval *rv0, const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+static size_t measure_...(const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+static int tobuf_...(buf *b, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+static int frombuf_...(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+static void parse_...(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+static void dump_...(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+
+const struct tvec_regty tvty_... = {
+ init_..., release_..., eq_..., measure_...,
+ tobuf_..., frombuf_...,
+ parse_..., dump_...
+};
+*/
+/*----- Signed and unsigned integer types ---------------------------------*/
+
+static void init_int(union tvec_regval *rv, const struct tvec_regdef *rd)
+ { rv->i = 0; }
+
+static void init_uint(union tvec_regval *rv, const struct tvec_regdef *rd)
+ { rv->u = 0; }
+
+static void release_int(union tvec_regval *rv, const struct tvec_regdef *rd)
+ { ; }
+
+static int eq_int(const union tvec_regval *rv0, const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+ { return (rv0->i == rv1->i); }
+
+static int eq_uint(const union tvec_regval *rv0,
+ const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+ { return (rv0->u == rv1->u); }
+
+static size_t measure_int(const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (8); }
+
+static int tobuf_int(buf *b, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (signed_to_buf(b, rv->i)); }
+
+static int tobuf_uint(buf *b, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (unsigned_to_buf(b, rv->u)); }
+
+static int frombuf_int(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return signed_from_buf(b, &rv->i); }
+
+static int frombuf_uint(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (unsigned_from_buf(b, &rv->u)); }
+
+static void parse_int(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ dstr d = DSTR_INIT;
+
+ tvec_readword(tv, &d, ";", "signed integer");
+ parse_signed(&rv->i, d.buf, rd->arg.p, tv);
+ tvec_flushtoeol(tv, 0);
+ dstr_destroy(&d);
+}
+
+static void parse_uint(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ dstr d = DSTR_INIT;
+
+ tvec_readword(tv, &d, ";", "unsigned integer");
+ parse_unsigned(&rv->u, d.buf, rd->arg.p, tv);
+ tvec_flushtoeol(tv, 0);
+ dstr_destroy(&d);
+}
+
+static void dump_int(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+{
+ unsigned long u;
+
+ tvec_write(tv, "%ld", rv->i);
+ if (!(style&TVSF_COMPACT)) {
+ if (rv->i >= 0) u = rv->i;
+ else u = -(unsigned long)rv->i;
+ tvec_write(tv, " ; = %s0x%0*lx", rv->i < 0 ? "-" : "", hex_width(u), u);
+ }
+}
+
+static void dump_uint(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+{
+ tvec_write(tv, "%lu", rv->u);
+ if (!(style&TVSF_COMPACT))
+ tvec_write(tv, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
+}
+
+const struct tvec_regty tvty_int = {
+ init_int, release_int, eq_int, measure_int,
+ tobuf_int, frombuf_int,
+ parse_int, dump_int
+};
+
+const struct tvec_irange
+ tvrange_schar = { SCHAR_MIN, SCHAR_MAX },
+ tvrange_short = { SHRT_MIN, SHRT_MAX },
+ tvrange_int = { INT_MIN, INT_MAX },
+ tvrange_long = { LONG_MIN, LONG_MAX },
+ tvrange_sbyte = { -128, 127 },
+ tvrange_i16 = { -32768, +32767 },
+ tvrange_i32 = { -2147483648, 2147483647 };
+
+const struct tvec_regty tvty_uint = {
+ init_uint, release_int, eq_uint, measure_int,
+ tobuf_uint, frombuf_uint,
+ parse_uint, dump_uint
+};
+
+const struct tvec_urange
+ tvrange_uchar = { 0, UCHAR_MAX },
+ tvrange_ushort = { 0, USHRT_MAX },
+ tvrange_uint = { 0, UINT_MAX },
+ tvrange_ulong = { 0, ULONG_MAX },
+ tvrange_size = { 0, (size_t)-1 },
+ tvrange_byte = { 0, 255 },
+ tvrange_u16 = { 0, 65535 },
+ tvrange_u32 = { 0, 4294967296 };
+
+int tvec_claimeq_int(struct tvec_state *tv, long i0, long i1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->in[0].v.i = i0; tv->out[0].v.i = i1;
+ return (tvec_claimeq(tv, &tvty_int, 0, file, lno, expr));
+}
+
+int tvec_claimeq_uint(struct tvec_state *tv,
+ unsigned long u0, unsigned long u1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->in[0].v.u = u0; tv->out[0].v.u = u1;
+ return (tvec_claimeq(tv, &tvty_uint, 0, file, lno, expr));
+}
+
+/*----- Enumerations ------------------------------------------------------*/
+
+static void init_enum(union tvec_regval *rv, const struct tvec_regdef *rd)
+{
+ const struct tvec_enuminfo *ei = rd->arg.p;
+
+ switch (ei->mv) {
+#define CASE(tag, ty, slot) \
+ case TVMISC_##tag: rv->slot = 0; break;
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+ default: abort();
+ }
+}
+
+static int eq_enum(const union tvec_regval *rv0,
+ const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+{
+ const struct tvec_enuminfo *ei = rd->arg.p;
+
+ switch (ei->mv) {
+#define CASE(tag, ty, slot) \
+ case TVMISC_##tag: return (rv0->slot == rv1->slot);
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+ default: abort();
+ }
+}
+
+static int tobuf_enum(buf *b, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+{
+ const struct tvec_enuminfo *ei = rd->arg.p;
+
+ switch (ei->mv) {
+#define CASE(tag, ty, slot) \
+ case TVMISC_##tag: return (HANDLE_##tag);
+#define HANDLE_INT signed_to_buf(b, rv->i)
+#define HANDLE_UINT unsigned_to_buf(b, rv->u)
+#define HANDLE_PTR -1
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+#undef HANDLE_INT
+#undef HANDLE_UINT
+#undef HANDLE_PTR
+ default: abort();
+ }
+ return (0);
+}
+
+static int frombuf_enum(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+{
+ const struct tvec_enuminfo *ei = rd->arg.p;
+
+ switch (ei->mv) {
+#define CASE(tag, ty, slot) \
+ case TVMISC_##tag: return (HANDLE_##tag);
+#define HANDLE_INT signed_from_buf(b, &rv->i)
+#define HANDLE_UINT unsigned_from_buf(b, &rv->u)
+#define HANDLE_PTR -1
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+#undef HANDLE_INT
+#undef HANDLE_UINT
+#undef HANDLE_PTR
+ default: abort();
+ }
+}
+
+static void parse_enum(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ const struct tvec_enuminfo *ei = rd->arg.p;
+#define DECLS(tag, ty, slot) \
+ const struct tvec_##slot##assoc *slot##a;
+ TVEC_MISCSLOTS(DECLS)
+#undef DECLS
+ dstr d = DSTR_INIT;
+
+ tvec_readword(tv, &d, ";", "enumeration tag or literal integer");
+ switch (ei->mv) {
+#define CASE(tag_, ty, slot) \
+ case TVMISC_##tag_: \
+ for (slot##a = ei->u.slot.av; slot##a->tag; slot##a++) \
+ if (STRCMP(d.buf, ==, slot##a->tag)) \
+ { rv->slot = FETCH_##tag_; goto end; }
+#define FETCH_INT (ia->i)
+#define FETCH_UINT (ua->u)
+#define FETCH_PTR ((/*unconst*/ void *)(pa->p))
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+#undef FETCH_INT
+#undef FETCH_UINT
+#undef FETCH_PTR
+ }
+
+ switch (ei->mv) {
+#define CASE(tag, ty, slot) \
+ case TVMISC_##tag: HANDLE_##tag goto end;
+#define HANDLE_INT parse_signed(&rv->i, d.buf, ei->u.i.ir, tv);
+#define HANDLE_UINT parse_unsigned(&rv->u, d.buf, ei->u.u.ur, tv);
+#define HANDLE_PTR if (STRCMP(d.buf, ==, "#nil")) rv->p = 0; \
+ else goto tagonly;
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+#undef HANDLE_INT
+#undef HANDLE_UINT
+#undef HANDLE_PTR
+ default: tagonly:
+ tvec_error(tv, "unknown `%s' value `%s'", ei->name, d.buf);
+ }
+
+end:
+ tvec_flushtoeol(tv, 0);
+ dstr_destroy(&d);
+}
+
+static void dump_enum(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+{
+ const struct tvec_enuminfo *ei = rd->arg.p;
+#define DECLS(tag, ty, slot) \
+ const struct tvec_##slot##assoc *slot##a;
+ TVEC_MISCSLOTS(DECLS)
+#undef DECLS
+ const char *tag;
+ unsigned long u;
+ unsigned f = 0;
+#define f_known 1u
+
+ switch (ei->mv) {
+#define CASE(tag_, ty, slot) \
+ case TVMISC_##tag_: \
+ for (slot##a = ei->u.slot.av; slot##a->tag; slot##a++) \
+ if (rv->slot == slot##a->slot) \
+ { tag = slot##a->tag; goto found; } \
+ break;
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+ default: abort();
+ }
+ goto print_int;
+
+found:
+ f |= f_known;
+ tvec_write(tv, "%s", tag);
+ if (style&TVSF_COMPACT) return;
+ tvec_write(tv, " ; = ");
+
+print_int:
+ switch (ei->mv) {
+#define CASE(tag, ty, slot) \
+ case TVMISC_##tag: HANDLE_##tag break;
+#define HANDLE_INT tvec_write(tv, "%ld", rv->i);
+#define HANDLE_UINT tvec_write(tv, "%lu", rv->u);
+#define HANDLE_PTR if (!rv->p) tvec_write(tv, "#nil"); \
+ else tvec_write(tv, "#<%s %p>", ei->name, rv->p);
+ TVEC_MISCSLOTS(CASE)
+#undef CASE
+#undef HANDLE_INT
+#undef HANDLE_UINT
+#undef HANDLE_PTR
+ }
+
+ switch (ei->mv) {
+ case TVMISC_INT:
+ if (!(f&f_known)) tvec_write(tv, " ;");
+ if (rv->i >= 0) u = rv->i;
+ else u = -(unsigned long)rv->i;
+ tvec_write(tv, " = %s0x%0*lx", rv->i < 0 ? "-" : "", hex_width(u), u);
+ break;
+ case TVMISC_UINT:
+ if (!(f&f_known)) tvec_write(tv, " ;");
+ tvec_write(tv, " = 0x%0*lx", hex_width(rv->u), rv->u);
+ break;
+ }
+}
+
+const struct tvec_regty tvty_enum = {
+ init_enum, release_int, eq_enum, measure_int,
+ tobuf_enum, frombuf_enum,
+ parse_enum, dump_enum
+};
+
+#define DEFCLAIM(tag, ty, slot) \
+ int tvec_claimeq_##slot##enum(struct tvec_state *tv, \
+ const struct tvec_enuminfo *ei, \
+ ty e0, ty e1, \
+ const char *file, unsigned lno, \
+ const char *expr) \
+ { \
+ union tvec_misc arg; \
+ \
+ assert(ei->mv == TVMISC_##tag); \
+ arg.p = ei; \
+ tv->in[0].v.slot = GET_##tag(e0); \
+ tv->out[0].v.slot = GET_##tag(e1); \
+ return (tvec_claimeq(tv, &tvty_enum, &arg, file, lno, expr)); \
+ }
+#define GET_INT(e) (e)
+#define GET_UINT(e) (e)
+#define GET_PTR(e) ((/*unconst*/ void *)(e))
+TVEC_MISCSLOTS(DEFCLAIM)
+#undef DEFCLAIM
+#undef GET_INT
+#undef GET_UINT
+#undef GET_PTR
+
+/*----- Flag types --------------------------------------------------------*/
+
+static void parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ const struct tvec_flaginfo *fi = rd->arg.p;
+ const struct tvec_flag *f;
+ unsigned long m = 0, v = 0, t;
+ dstr d = DSTR_INIT;
+ int ch;
+
+ for (;;) {
+ DRESET(&d); tvec_readword(tv, &d, "|;", "flag name or integer");
+
+ for (f = fi->fv; f->tag; f++)
+ if (STRCMP(f->tag, ==, d.buf)) {
+ if (m&f->m) tvec_error(tv, "colliding flag setting");
+ else { m |= f->m; v |= f->v; goto next; }
+ }
+
+ parse_unsigned(&t, d.buf, fi->range, tv); v |= t;
+ next:
+ if (tvec_nexttoken(tv)) break;
+ ch = getc(tv->fp); if (ch != '|') tvec_syntax(tv, ch, "`|'");
+ if (tvec_nexttoken(tv)) tvec_syntax(tv, '\n', "flag name or integer");
+ }
+ rv->u = v;
+}
+
+static void dump_flags(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+{
+ const struct tvec_flaginfo *fi = rd->arg.p;
+ const struct tvec_flag *f;
+ unsigned long m = ~(unsigned long)0, v = rv->u;
+ const char *sep;
+
+ for (f = fi->fv, sep = ""; f->tag; f++)
+ if ((m&f->m) && (v&f->m) == f->v) {
+ tvec_write(tv, "%s%s", sep, f->tag); m &= ~f->m;
+ sep = style&TVSF_COMPACT ? "|" : " | ";
+ }
+
+ if (v&m) tvec_write(tv, "%s0x%0*lx", sep, hex_width(v), v&m);
+
+ if (!(style&TVSF_COMPACT))
+ tvec_write(tv, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
+}
+
+const struct tvec_regty tvty_flags = {
+ init_uint, release_int, eq_uint, measure_int,
+ tobuf_uint, frombuf_uint,
+ parse_flags, dump_flags
+};
+
+int tvec_claimeq_flags(struct tvec_state *tv,
+ const struct tvec_flaginfo *fi,
+ unsigned long f0, unsigned long f1,
+ const char *file, unsigned lno, const char *expr)
+{
+ union tvec_misc arg;
+
+ arg.p = fi; tv->in[0].v.u = f0; tv->out[0].v.u = f1;
+ return (tvec_claimeq(tv, &tvty_flags, &arg, file, lno, expr));
+}
+
+/*----- Text and byte strings ---------------------------------------------*/
+
+void tvec_allocstring(union tvec_regval *rv, size_t sz)
+{
+ if (rv->str.sz < sz) { xfree(rv->str.p); rv->str.p = xmalloc(sz); }
+ rv->str.sz = sz;
+}
+
+void tvec_allocbytes(union tvec_regval *rv, size_t sz)
+{
+ if (rv->bytes.sz < sz) { xfree(rv->bytes.p); rv->bytes.p = xmalloc(sz); }
+ rv->bytes.sz = sz;
+}
+
+static void init_string(union tvec_regval *rv, const struct tvec_regdef *rd)
+ { rv->str.p = 0; rv->str.sz = 0; }
+
+static void init_bytes(union tvec_regval *rv, const struct tvec_regdef *rd)
+ { rv->bytes.p = 0; rv->bytes.sz = 0; }
+
+static void release_string(union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { xfree(rv->str.p); }
+
+static void release_bytes(union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { xfree(rv->bytes.p); }
+
+static int eq_string(const union tvec_regval *rv0,
+ const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+{
+ return (rv0->str.sz == rv1->str.sz &&
+ (!rv0->bytes.sz ||
+ MEMCMP(rv0->str.p, ==, rv1->str.p, rv1->str.sz)));
+}
+
+static int eq_bytes(const union tvec_regval *rv0,
+ const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+{
+ return (rv0->bytes.sz == rv1->bytes.sz &&
+ (!rv0->bytes.sz ||
+ MEMCMP(rv0->bytes.p, ==, rv1->bytes.p, rv1->bytes.sz)));
+}
+
+static size_t measure_string(const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (rv->str.sz + 4); }
+
+static size_t measure_bytes(const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (rv->bytes.sz + 4); }
+
+static int tobuf_string(buf *b, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (buf_putmem32l(b, rv->str.p, rv->str.sz)); }
+
+static int tobuf_bytes(buf *b, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (buf_putmem32l(b, rv->bytes.p, rv->bytes.sz)); }
+
+static int frombuf_string(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+{
+ const void *p;
+ size_t sz;
+
+ p = buf_getmem32l(b, &sz); if (!p) return (-1);
+ tvec_allocstring(rv, sz); memcpy(rv->str.p, p, sz);
+ return (0);
+}
+
+static int frombuf_bytes(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+{
+ const void *p;
+ size_t sz;
+
+ p = buf_getmem32l(b, &sz); if (!p) return (-1);
+ tvec_allocbytes(rv, sz); memcpy(rv->bytes.p, p, sz);
+ return (0);
+}
+
+static void check_string_length(size_t sz, const struct tvec_urange *ur,
+ struct tvec_state *tv)
+{
+ if (ur && (ur->min > sz || sz > ur->max))
+ tvec_error(tv, "invalid string length %lu; must be in [%lu..%lu]",
+ (unsigned long)sz, ur->min, ur->max);
+}
+
+static void parse_string(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ void *p = rv->str.p;
+
+ read_compound_string(&p, &rv->str.sz, TVCODE_BARE, tv); rv->str.p = p;
+ check_string_length(rv->str.sz, rd->arg.p, tv);
+}
+
+static void parse_bytes(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ void *p = rv->bytes.p;
+
+ read_compound_string(&p, &rv->bytes.sz, TVCODE_HEX, tv); rv->bytes.p = p;
+ check_string_length(rv->bytes.sz, rd->arg.p, tv);
+}
+
+static void dump_string(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+{
+ const unsigned char *p, *q, *l;
+ int ch;
+ unsigned f = 0;
+#define f_nonword 1u
+#define f_newline 2u
+
+ if (!rv->str.sz) { tvec_write(tv, "\"\""); return; }
+
+ p = (const unsigned char *)rv->str.p; l = p + rv->str.sz;
+ if (*p == '!' || *p == ';' || *p == '"' || *p == '\'') goto quote;
+ for (q = p; q < l; q++)
+ if (*q == '\n' && q != l - 1) f |= f_newline;
+ else if (!*q || !isgraph(*q) || *q == '\\') f |= f_nonword;
+ if (f&f_newline) { tvec_write(tv, "\n\t"); goto quote; }
+ else if (f&f_nonword) goto quote;
+ tv->output->ops->write(tv->output, (const char *)p, rv->str.sz); return;
+
+quote:
+ tvec_write(tv, "\"");
+ for (q = p; q < l; q++)
+ switch (*q) {
+ case '"': case '\\': ch = *q; goto escape;
+ case '\a': ch = 'a'; goto escape;
+ case '\b': ch = 'b'; goto escape;
+ case '\x1b': ch = 'e'; goto escape;
+ case '\f': ch = 'f'; goto escape;
+ case '\r': ch = 'r'; goto escape;
+ case '\t': ch = 't'; goto escape;
+ case '\v': ch = 'v'; goto escape;
+ escape:
+ if (p < q)
+ tv->output->ops->write(tv->output, (const char *)p, q - p);
+ tvec_write(tv, "\\%c", ch); p = q + 1;
+ break;
+
+ case '\n':
+ if (p < q)
+ tv->output->ops->write(tv->output, (const char *)p, q - p);
+ tvec_write(tv, "\\n"); p = q + 1;
+ if (!(style&TVSF_COMPACT) && q < l) tvec_write(tv, "\"\t\"");
+ break;
+
+ default:
+ if (isprint(*q)) break;
+ if (p < q)
+ tv->output->ops->write(tv->output, (const char *)p, q - p);
+ tvec_write(tv, "\\x{%0*x}", hex_width(UCHAR_MAX), *q); p = q + 1;
+ break;
+ }
+ if (p < q) tv->output->ops->write(tv->output, (const char *)p, q - p);
+ tvec_write(tv, "\"");
+
+#undef f_nonword
+#undef f_newline
+}
+
+static void dump_bytes(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+{
+ const unsigned char *p = rv->bytes.p, *l = p + rv->bytes.sz;
+ size_t off, sz = rv->bytes.sz;
+ unsigned i, n;
+ int wd;
+
+ if (!sz) {
+ tvec_write(tv, style&TVSF_COMPACT ? "\"\"" : "\"\" ; empty");
+ return;
+ }
+
+ if (style&TVSF_COMPACT) {
+ while (p < l) tvec_write(tv, "%02x", *p++);
+ return;
+ }
+
+ if (sz > 16) tvec_write(tv, "\n\t");
+
+ off = 0; wd = hex_width(sz);
+ while (p < l) {
+ if (l - p < 16) n = l - p;
+ else n = 16;
+
+ for (i = 0; i < 16; i++) {
+ if (i < n) tvec_write(tv, "%02x", p[i]);
+ else tvec_write(tv, " ");
+ if (i%4 == 3) tvec_write(tv, " ");
+ }
+ tvec_write(tv, " ; ");
+ if (sz > 16) tvec_write(tv, "[%0*lx] ", wd, (unsigned long)off);
+ for (i = 0; i < n; i++)
+ tvec_write(tv, "%c", isprint(p[i]) ? p[i] : '.');
+ p += n; off += n;
+ if (p < l) tvec_write(tv, "\n\t");
+ }
+}
+
+const struct tvec_regty tvty_string = {
+ init_string, release_string, eq_string, measure_string,
+ tobuf_string, frombuf_string,
+ parse_string, dump_string
+};
+
+const struct tvec_regty tvty_bytes = {
+ init_bytes, release_bytes, eq_bytes, measure_bytes,
+ tobuf_bytes, frombuf_bytes,
+ parse_bytes, dump_bytes
+};
+
+int tvec_claimeq_string(struct tvec_state *tv,
+ const char *p0, size_t sz0,
+ const char *p1, size_t sz1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->in[0].v.str.p = (/*unconst*/ char *)p0; tv->in[0].v.str.sz = sz0;
+ tv->out[0].v.str.p =(/*unconst*/ char *) p1; tv->out[0].v.str.sz = sz1;
+ return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
+}
+
+int tvec_claimeq_strz(struct tvec_state *tv,
+ const char *p0, const char *p1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->in[0].v.str.p = (/*unconst*/ char *)p0;
+ tv->in[0].v.str.sz = strlen(p0);
+ tv->out[0].v.str.p = (/*unconst*/ char *)p1;
+ tv->out[0].v.str.sz = strlen(p1);
+ return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
+}
+
+int tvec_claimeq_bytes(struct tvec_state *tv,
+ const void *p0, size_t sz0,
+ const void *p1, size_t sz1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->in[0].v.bytes.p = (/*unconst*/ void *)p0;
+ tv->in[0].v.bytes.sz = sz0;
+ tv->out[0].v.bytes.p = (/*unconst*/ void *)p1;
+ tv->out[0].v.bytes.sz = sz1;
+ return (tvec_claimeq(tv, &tvty_bytes, 0, file, lno, expr));
+}
+
+/*----- Buffer type -------------------------------------------------------*/
+
+static int eq_buffer(const union tvec_regval *rv0,
+ const union tvec_regval *rv1,
+ const struct tvec_regdef *rd)
+ { return (rv0->bytes.sz == rv1->bytes.sz); }
+
+static int tobuf_buffer(buf *b, const union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+ { return (unsigned_to_buf(b, rv->bytes.sz)); }
+
+static int frombuf_buffer(buf *b, union tvec_regval *rv,
+ const struct tvec_regdef *rd)
+{
+ unsigned long u;
+
+ if (unsigned_from_buf(b, &u)) return (-1);
+ if (u > (size_t)-1) return (-1);
+ tvec_allocbytes(rv, u); memset(rv->bytes.p, '!', u);
+ return (0);
+}
+
+static const char units[] = "kMGTPEZY";
+
+static void parse_buffer(union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ dstr d = DSTR_INIT;
+ char *q; const char *unit;
+ int olderr;
+ size_t pos;
+ unsigned long u, t;
+ unsigned f = 0;
+#define f_range 1u
+
+ tvec_readword(tv, &d, ";", "buffer length");
+ olderr = errno; errno = 0;
+ u = strtoul(d.buf, &q, 0);
+ if (errno) goto bad;
+ errno = olderr;
+ if (!*q) {
+ tvec_skipspc(tv); pos = d.len;
+ if (!tvec_readword(tv, &d, ";", 0)) pos++;
+ q = d.buf + pos;
+ }
+
+ if (u > (size_t)-1) goto rangerr;
+ for (t = u, unit = units; *unit; unit++) {
+ if (t > (size_t)-1/1024) f |= f_range;
+ else t *= 1024;
+ if (*q == *unit && (!q[1] || q[1] == 'B')) {
+ if (f&f_range) goto rangerr;
+ u = t; q += 2; break;
+ }
+ }
+ if (*q && *q != ';') goto bad;
+ check_string_length(u, rd->arg.p, tv);
+
+ tvec_flushtoeol(tv, 0);
+ tvec_allocbytes(rv, u); memset(rv->bytes.p, 0, u);
+ DDESTROY(&d); return;
+
+bad:
+ tvec_error(tv, "invalid buffer length `%s'", d.buf);
+
+rangerr:
+ tvec_error(tv, "buffer length `%s' out of range", d.buf);
+
+#undef f_range
+}
+
+static void dump_buffer(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ struct tvec_state *tv, unsigned style)
+{
+ const char *unit;
+ unsigned long u = rv->bytes.sz;
+
+ if (!u || u%1024)
+ tvec_write(tv, "%lu B", u);
+ else {
+ for (unit = units, u /= 1024; !(u%1024) && unit[1]; u /= 1024, unit++);
+ tvec_write(tv, "%lu %cB", u, *unit);
+ }
+}
+
+const struct tvec_regty tvty_buffer = {
+ init_bytes, release_bytes, eq_buffer, measure_int,
+ tobuf_buffer, frombuf_buffer,
+ parse_buffer, dump_buffer
+};
+
+/*----- That's all, folks -------------------------------------------------*/