@@@ man wip
[mLib] / test / tvec-types.c
index e76d4a1..068d565 100644 (file)
@@ -107,7 +107,7 @@ static int unsigned_from_buf(buf *b, unsigned long *u_out)
 
   ASSIGN64(ulmax, ULONG_MAX);
   if (buf_getk64l(b, &k)) return (-1);
-  if (CMP64(k, >, ulmax)) return (-1);
+  if (CMP64(k, >, ulmax)) { buf_break(b); return (-1); }
   *u_out = GET64(unsigned long, k); return (0);
 }
 
@@ -161,7 +161,7 @@ static int signed_from_buf(buf *b, long *i_out)
   else {
     CPL64(k, k);
     if (CMP64(k, <=, not_lmin)) *i_out = -(long)GET64(unsigned long, k) - 1;
-    else return (-1);
+    else { buf_break(b); return (-1); }
   }
   return (0);
 }
@@ -387,6 +387,90 @@ static int parse_signed(long *i_out, const char *p,
   if (check_signed_range(i, ir, tv)) return (-1);
   *i_out = i; return (0);
 }
+static const char size_units[] = "kMGTPEZY";
+
+/* --- @parse_size@ --- *
+ *
+ * Arguments:  @struct tvec_state *tv@ = test-vector state
+ *             @size_t *u_out@ = where to put the answer
+ *             @const char *delims@ = delimiters
+ *             @const char *what@ = description of what we're parsing
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Parse a memory size.
+ */
+
+static int parse_size(struct tvec_state *tv, size_t *u_out,
+                     const char *delims, const char *what)
+{
+  dstr d = DSTR_INIT;
+  const char *p, *unit;
+  unsigned long u, t;
+  int rc;
+  unsigned f = 0;
+#define f_range 1u
+
+  if (tvec_readword(tv, &d, 0, delims, what)) { rc = -1; goto end; }
+  p = d.buf;
+  if (parse_unsigned_integer(&u, &p, p)) goto bad;
+  if (!*p) tvec_readword(tv, &d, &p, delims, 0);
+
+  if (u > (size_t)-1) goto rangerr;
+  for (t = u, unit = size_units; *unit; unit++) {
+    if (t > (size_t)-1/1024) f |= f_range;
+    else t *= 1024;
+    if (*p == *unit) {
+      if (f&f_range) goto rangerr;
+      u = t; p++; break;
+    }
+  }
+  if (*p == 'B') p++;
+  if (*p) goto bad;
+
+  *u_out = u; rc = 0;
+end:
+  dstr_destroy(&d);
+  return (rc);
+
+bad:
+  tvec_error(tv, "invalid %s `%s'", what, d.buf);
+  rc = -1; goto end;
+
+rangerr:
+  tvec_error(tv, "%s `%s' out of range", what, d.buf);
+  rc = -1; goto end;
+
+#undef f_range
+}
+
+/* --- @format_size@ --- *
+ *
+ * Arguments:  @const struct gprintf_ops *gops@ = print operations
+ *             @void *go@ = print destination
+ *             @unsigned long u@ = a size
+ *             @unsigned style@ = style (@TVSF_...@)
+ *
+ * Returns:    ---
+ *
+ * Use:                Format @u@ as a size in bytes to the destination, expressing
+ *             it with a unit prefix if this is possible exactly.
+ */
+
+static void format_size(const struct gprintf_ops *gops, void *go,
+                       unsigned long u, unsigned style)
+{
+  const char *unit;
+
+  if (!u || u%1024)
+    gprintf(gops, go, "%lu%sB", u, style&TVSF_COMPACT ? "" : " ");
+  else {
+    for (unit = size_units, u /= 1024;
+        !(u%1024) && unit[1];
+        u /= 1024, unit++);
+    gprintf(gops, go, "%lu%s%cB", u, style&TVSF_COMPACT ? "" : " ", *unit);
+  }
+}
 
 /*----- Floating-point utilities ------------------------------------------*/
 
@@ -534,8 +618,8 @@ static void format_floating(const struct gprintf_ops *gops, void *go,
  *
  * Use:                Parse a floating-point number from a string.  Reports any
  *             necessary errors.  If @q_out@ is not null then trailing
- *             material is permitted and a pointer to it is left in
- *             @*q_out@; this will be null if there is no trailing material.
+ *             material is permitted and a pointer to it (or the end of the
+ *             string) is left in @*q_out@.
  */
 
 static int parse_floating(double *x_out, const char **q_out, const char *p,
@@ -547,11 +631,10 @@ static int parse_floating(double *x_out, const char **q_out, const char *p,
   double x;
   int olderr, rc;
 
-  if (q_out) *q_out = 0;
-
   /* Check for special tokens. */
   if (STRCMP(p, ==, "#nan")) {
 #ifdef NAN
+    if (q_out) *q_out = p + strlen(p);
     x = NAN; rc = 0;
 #else
     tvec_error(tv, "NaN not supported on this system");
@@ -562,6 +645,7 @@ static int parse_floating(double *x_out, const char **q_out, const char *p,
   else if (STRCMP(p, ==, "#inf") ||
           STRCMP(p, ==, "#+inf") || STRCMP(p, ==, "+#inf")) {
 #ifdef INFINITY
+    if (q_out) *q_out = p + strlen(p);
     x = INFINITY; rc = 0;
 #else
     tvec_error(tv, "infinity not supported on this system");
@@ -571,6 +655,7 @@ static int parse_floating(double *x_out, const char **q_out, const char *p,
 
   else if (STRCMP(p, ==, "#-inf") || STRCMP(p, ==, "-#inf")) {
 #ifdef INFINITY
+    if (q_out) *q_out = p + strlen(p);
     x = -INFINITY; rc = 0;
 #else
     tvec_error(tv, "infinity not supported on this system");
@@ -593,9 +678,8 @@ static int parse_floating(double *x_out, const char **q_out, const char *p,
     /* Parse the number using the system parser. */
     olderr = errno; errno = 0;
     x = strtod(p, &q);
-    if (!*q) /* nothing to do */;
-    else if (q_out) *q_out = q;
-    else { tvec_syntax(tv, *q, "end-of-line"); rc = -1; goto end; }
+    if (q_out) *q_out = q;
+    else if (*q) { tvec_syntax(tv, *q, "end-of-line"); rc = -1; goto end; }
     if (errno && (errno != ERANGE || (x > 0 ? -x : x) == HUGE_VAL)) {
       tvec_error(tv, "invalid floating-point number `%.*s': %s",
                 (int)(q - p), p, strerror(errno));
@@ -1135,7 +1219,7 @@ static int read_compound_string(void **p_inout, size_t *sz_inout,
        ungetc(ch, tv->fp);
        if (cdc && flush_codec(cdc, &d, tv)) { rc = -1; goto end; }
        cdc = 0;
-       DRESET(&w); tvec_readword(tv, &w, ";", "character name");
+       DRESET(&w); tvec_readword(tv, &w, 0, ";", "character name");
        if (read_charname(&ch, w.buf, RCF_EOFOK)) {
          rc = tvec_error(tv, "unknown character name `%s'", d.buf);
          goto end;
@@ -1148,7 +1232,7 @@ static int read_compound_string(void **p_inout, size_t *sz_inout,
        if (cdc && flush_codec(cdc, &d, tv)) { rc = -1; goto end; }
        cdc = 0;
        ungetc(ch, tv->fp);
-       DRESET(&w); tvec_readword(tv, &w, ";", "`!'-keyword");
+       DRESET(&w); tvec_readword(tv, &w, 0, ";", "`!'-keyword");
 
        /* Change bareword coding system. */
        if (STRCMP(w.buf, ==, "!bare"))
@@ -1167,7 +1251,7 @@ static int read_compound_string(void **p_inout, size_t *sz_inout,
            goto end;
          }
          DRESET(&w);
-         if (tvec_readword(tv, &w, ";{", "repeat count"))
+         if (tvec_readword(tv, &w, 0, ";{", "repeat count"))
            { rc = -1; goto end;  }
          if (parse_unsigned_integer(&n, &q, w.buf)) {
            rc = tvec_error(tv, "invalid repeat count `%s'", w.buf);
@@ -1213,7 +1297,8 @@ static int read_compound_string(void **p_inout, size_t *sz_inout,
          default:
            assert(ccl);
            ungetc(ch, tv->fp); DRESET(&w);
-           if (tvec_readword(tv, &w, ";", "%s-encoded fragment", ccl->name))
+           if (tvec_readword(tv, &w, 0, ";",
+                             "%s-encoded fragment", ccl->name))
              { rc = -1; goto end; }
            if (!cdc) cdc = ccl->decoder(cdf);
            err = cdc->ops->code(cdc, w.buf, w.len, &d);
@@ -1370,7 +1455,8 @@ static int parse_int(union tvec_regval *rv, const struct tvec_regdef *rd,
   dstr d = DSTR_INIT;
   int rc;
 
-  if (tvec_readword(tv, &d, ";", "signed integer")) { rc = -1; goto end; }
+  if (tvec_readword(tv, &d, 0, ";", "signed integer"))
+    { rc = -1; goto end; }
   if (parse_signed(&rv->i, d.buf, rd->arg.p, tv)) { rc = -1; goto end; }
   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
   rc = 0;
@@ -1385,7 +1471,8 @@ static int parse_uint(union tvec_regval *rv, const struct tvec_regdef *rd,
   dstr d = DSTR_INIT;
   int rc;
 
-  if (tvec_readword(tv, &d, ";", "unsigned integer")) { rc = -1; goto end; }
+  if (tvec_readword(tv, &d, 0, ";", "unsigned integer"))
+    { rc = -1; goto end; }
   if (parse_unsigned(&rv->u, d.buf, rd->arg.p, tv)) { rc = -1; goto end; }
   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
   rc = 0;
@@ -1612,7 +1699,7 @@ static int parse_float(union tvec_regval *rv, const struct tvec_regdef *rd,
   dstr d = DSTR_INIT;
   int rc;
 
-  if (tvec_readword(tv, &d, ";", "floating-point number"))
+  if (tvec_readword(tv, &d, 0, ";", "floating-point number"))
     { rc = -1; goto end; }
   if (parse_floating(&rv->f, 0, d.buf, rd->arg.p, tv))
     { rc = -1; goto end; }
@@ -1806,21 +1893,17 @@ static int parse_duration(union tvec_regval *rv,
 {
   const struct duration_unit *u;
   const char *q;
-  dstr d = DSTR_INIT; size_t pos;
+  dstr d = DSTR_INIT;
   double t;
   int rc;
 
-  if (tvec_readword(tv, &d, ";", "duration")) { rc = -1; goto end; }
+  if (tvec_readword(tv, &d, 0, ";", "duration")) { rc = -1; goto end; }
   if (parse_floating(&t, &q, d.buf,
                     rd->arg.p ? rd->arg.p : &tvflt_nonneg, tv))
     { rc = -1; goto end; }
 
-  if (!q) {
-    tvec_skipspc(tv); pos = d.len;
-    if (!tvec_readword(tv, &d, ";", 0)) q = d.buf + pos + 1;
-  }
-
-  if (q) {
+  if (!*q) tvec_readword(tv, &d, &q, ";", 0);
+  if (*q) {
     for (u = duration_units; u->unit; u++)
       if (STRCMP(q, ==, u->unit)) { t *= u->scale; goto found_unit; }
     rc = tvec_syntax(tv, *q, "end-of-line"); goto end;
@@ -2001,7 +2084,7 @@ static int frombuf_penum(buf *b, union tvec_regval *rv,
   if (signed_from_buf(b, &i)) return (-1);
   if (0 <= i && i < n) rv->p = (/*unconst*/ void *)pei->av[i].p;
   else if (i == -1) rv->p = 0;
-  else return (-1);
+  else { buf_break(b); return (-1); }
   return (0);
 }
 
@@ -2032,7 +2115,7 @@ static int frombuf_penum(buf *b, union tvec_regval *rv,
     dstr d = DSTR_INIT;                                                        \
     int rc;                                                            \
                                                                        \
-    if (tvec_readword(tv, &d, ";", "enumeration tag or " LITSTR_##tag_)) \
+    if (tvec_readword(tv, &d, 0, ";", "enumeration tag or " LITSTR_##tag_)) \
       { rc = -1; goto end; }                                           \
     for (a = ei->av; a->tag; a++)                                      \
       if (STRCMP(a->tag, ==, d.buf)) { FOUND_##tag_ goto done; }       \
@@ -2293,7 +2376,7 @@ static int parse_flags(union tvec_regval *rv, const struct tvec_regdef *rd,
 
     /* Read the next item. */
     DRESET(&d);
-    if (tvec_readword(tv, &d, "|;", "flag name or integer"))
+    if (tvec_readword(tv, &d, 0, "|;", "flag name or integer"))
       { rc = -1; goto end; }
 
     /* Try to find a matching entry in the table. */
@@ -2436,7 +2519,7 @@ static int tobuf_char(buf *b, const union tvec_regval *rv,
 
   if (0 <= rv->i && rv->i <= UCHAR_MAX) u = rv->i;
   else if (rv->i == EOF) u = MASK32;
-  else return (-1);
+  else { buf_break(b); return (-1); }
   return (buf_putu32l(b, u));
 }
 
@@ -2462,7 +2545,7 @@ static int frombuf_char(buf *b, union tvec_regval *rv,
   if (buf_getu32l(b, &u)) return (-1);
   if (0 <= u && u <= UCHAR_MAX) rv->i = u;
   else if (u == MASK32) rv->i = EOF;
-  else return (-1);
+  else { buf_break(b); return (-1); }
   return (0);
 }
 
@@ -2553,7 +2636,8 @@ static int parse_char(union tvec_regval *rv, const struct tvec_regdef *rd,
      */
 
     ungetc(ch, tv->fp);
-    if (tvec_readword(tv, &d, ";", "character name")) { rc = -1; goto end; }
+    if (tvec_readword(tv, &d, 0, ";", "character name"))
+      { rc = -1; goto end; }
     if (STRCMP(d.buf, !=, "#")) {
       if (read_charname(&ch, d.buf, RCF_EOFOK)) {
        rc = tvec_error(tv, "unknown character name `%s'", d.buf);
@@ -3182,7 +3266,37 @@ void tvec_allocbytes(union tvec_regval *rv, size_t sz)
 
 /*----- Buffer type -------------------------------------------------------*/
 
-/* Buffers are initialized and released as binary strings. */
+/* --- @init_buffer@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a register value.
+ *
+ *             Buffer values values are initialized with a null pointer,
+ *             zero length, and zero residue, modulus, and offset.
+ */
+
+static void init_buffer(union tvec_regval *rv, const struct tvec_regdef *rd)
+  { rv->buf.p = 0; rv->buf.sz = rv->buf.a = rv->buf.m = rv->buf.off = 0; }
+
+/* --- @release_buffer@, @release_bytes@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *
+ * Returns:    ---
+ *
+ * Use:                Release resources held by a register value.
+ *
+ *             Buffers are freed.
+ */
+
+static void release_buffer(union tvec_regval *rv,
+                          const struct tvec_regdef *rd)
+  { if (rv->buf.p) xfree(rv->buf.p - rv->buf.off); }
 
 /* --- @eq_buffer@ --- *
  *
@@ -3193,14 +3307,19 @@ void tvec_allocbytes(union tvec_regval *rv, size_t sz)
  *
  * Use:                Compare register values for equality.
  *
- *             Buffer values are equal if and only if their sizes are equal;
- *             their contents are %%\emph{not}%% compared.
+ *             Buffer values are equal if and only if their sizes and
+ *             alignment parameters are equal; their contents are
+ *             %%\emph{not}%% compared.
  */
 
 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); }
+{
+  return (rv0->buf.sz == rv1->buf.sz &&
+         rv0->buf.a == rv1->buf.a &&
+         rv0->buf.m == rv1->buf.m);
+}
 
 /* --- @tobuf_buffer@ --- *
  *
@@ -3212,27 +3331,17 @@ static int eq_buffer(const union tvec_regval *rv0,
  *
  * Use:                Serialize a register value to a buffer.
  *
- *             Buffer values are serialized as just their lengths, as
- *             unsigned integers.
+ *             Buffer values are serialized as their lengths, residues, and
+ *             moduli, as unsigned integers.
  */
 
 static int tobuf_buffer(buf *b, const union tvec_regval *rv,
                         const struct tvec_regdef *rd)
-  { return (unsigned_to_buf(b, rv->bytes.sz)); }
-
-/* --- @allocate_buffer@ --- *
- *
- * Arguments:  @union tvec_regval *rv@ = register value
- *             @size_t sz@ = size to allocate
- *
- * Returns:    ---
- *
- * Use:                Allocate @sz@ bytes to the buffer and fill the space with a
- *             distinctive pattern.
- */
-
-static void allocate_buffer(union tvec_regval *rv, size_t sz)
-  { tvec_allocbytes(rv, sz); memset(rv->bytes.p, '?', sz); }
+{
+  return (unsigned_to_buf(b, rv->buf.sz) ||
+         unsigned_to_buf(b, rv->buf.a) ||
+         unsigned_to_buf(b, rv->buf.m));
+}
 
 /* --- @frombuf_buffer@ --- *
  *
@@ -3252,11 +3361,14 @@ static void allocate_buffer(union tvec_regval *rv, size_t sz)
 static int frombuf_buffer(buf *b, union tvec_regval *rv,
                          const struct tvec_regdef *rd)
 {
-  unsigned long u;
+  unsigned long sz, a, m;
 
-  if (unsigned_from_buf(b, &u)) return (-1);
-  if (u > (size_t)-1) return (-1);
-  allocate_buffer(rv, u);
+  if (unsigned_from_buf(b, &sz)) return (-1);
+  if (unsigned_from_buf(b, &a)) return (-1);
+  if (unsigned_from_buf(b, &m)) return (-1);
+  if (sz > (size_t)-1 || a > (size_t)-1 || m > (size_t)-1)
+    { buf_break(b); return (-1); }
+  rv->buf.sz = sz; rv->buf.a = a; rv->buf.m = m;
   return (0);
 }
 
@@ -3279,56 +3391,42 @@ static int frombuf_buffer(buf *b, union tvec_regval *rv,
  *             pattern.
  */
 
-static const char units[] = "kMGTPEZY";
-
 static int parse_buffer(union tvec_regval *rv,
                        const struct tvec_regdef *rd,
                        struct tvec_state *tv)
 {
-  dstr d = DSTR_INIT;
-  const char *q, *unit;
-  size_t pos;
-  unsigned long u, t;
-  int rc;
-  unsigned f = 0;
-#define f_range 1u
+  unsigned long sz, a = 0, m = 0;
+  int ch, rc;
 
-  if (tvec_readword(tv, &d, ";", "buffer length")) { rc = -1; goto end; }
-  if (parse_unsigned_integer(&u, &q, d.buf)) goto bad;
-  if (!*q) {
-    tvec_skipspc(tv); pos = d.len;
-    if (!tvec_readword(tv, &d, ";", 0)) pos++;
-    q = d.buf + pos;
-  }
+  if (parse_size(tv, &sz, "@;", "buffer length")) { rc = -1; goto end; }
+  if (check_string_length(sz, rd->arg.p, tv)) { rc = -1; goto end; }
 
-  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) {
-      if (f&f_range) goto rangerr;
-      u = t; q++; break;
-    }
+  tvec_skipspc(tv);
+  ch = getc(tv->fp);
+  if (ch == ';' || ch == '\n') { ungetc(ch, tv->fp); goto done; }
+  else if (ch != '@') { rc = tvec_syntax(tv, ch, "`@'"); goto end; }
+
+  if (parse_size(tv, &m, "+;", "alignment quantum")) { rc = -1; goto end; }
+  if (m == 1) m = 0;
+
+  tvec_skipspc(tv);
+  ch = getc(tv->fp);
+  if (ch == ';' || ch == '\n') { ungetc(ch, tv->fp); goto done; }
+  else if (ch != '+') { rc = tvec_syntax(tv, ch, "`+'"); goto end; }
+
+  if (parse_size(tv, &a, ";", "alignment offset")) { rc = -1; goto end; }
+  if (a >= m) {
+    rc = tvec_error(tv, "alignment offset %lu >= quantum %lu",
+                   (unsigned long)a, (unsigned long)m);
+    goto end;
   }
-  if (*q == 'B') q++;
-  if (*q) goto bad;
-  if (check_string_length(u, rd->arg.p, tv)) { rc = -1; goto end; }
 
+done:
   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
-  allocate_buffer(rv, u);
+  rv->buf.sz = sz; rv->buf.a = a; rv->buf.m = m;
   rc = 0;
 end:
-  DDESTROY(&d); return (rc);
-
-bad:
-  tvec_error(tv, "invalid buffer length `%s'", d.buf);
-  rc = -1; goto end;
-
-rangerr:
-  tvec_error(tv, "buffer length `%s' out of range", d.buf);
-  rc = -1; goto end;
-
-#undef f_range
+  return (rc);
 }
 
 /* --- @dump_buffer@ --- *
@@ -3352,22 +3450,80 @@ static void dump_buffer(const union tvec_regval *rv,
                        unsigned style,
                        const struct gprintf_ops *gops, void *go)
 {
-  const char *unit;
-  unsigned long u = rv->bytes.sz;
-
-  if (!u || u%1024)
-    gprintf(gops, go, "%lu B", u);
-  else {
-    for (unit = units, u /= 1024; !(u%1024) && unit[1]; u /= 1024, unit++);
-    gprintf(gops, go, "%lu %cB", u, *unit);
+  format_size(gops, go, rv->buf.sz, style);
+  if (rv->buf.m) {
+    gprintf(gops, go, style&TVSF_COMPACT ? "@" : " @ ");
+    format_size(gops, go, rv->buf.m, style);
+    if (rv->buf.a) {
+      gprintf(gops, go, style&TVSF_COMPACT ? "+" : " + ");
+      format_size(gops, go, rv->buf.a, style);
+    }
+  }
+  if (!(style&TVSF_COMPACT)) {
+    gprintf(gops, go, " ; = %lu", rv->buf.sz);
+    if (rv->buf.m) {
+      gprintf(gops, go, " @ %lu", rv->buf.m);
+      if (rv->buf.a) gprintf(gops, go, " + %lu", rv->buf.a);
+    }
+    gprintf(gops, go, " = "); format_unsigned_hex(gops, go, rv->buf.sz);
+    if (rv->buf.m) {
+      gprintf(gops, go, " @ "); format_unsigned_hex(gops, go, rv->buf.m);
+      if (rv->buf.a) {
+       gprintf(gops, go, " + ");
+       format_unsigned_hex(gops, go, rv->buf.a);
+      }
+    }
   }
 }
 
 /* Buffer type definition. */
 const struct tvec_regty tvty_buffer = {
-  init_bytes, release_bytes, eq_buffer,
+  init_buffer, release_buffer, eq_buffer,
   tobuf_buffer, frombuf_buffer,
   parse_buffer, dump_buffer
 };
 
+/* --- @tvec_initbuffer@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const union tvec_regval *src@ = source buffer
+ *             @size_t sz@ = size to allocate
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize the alignment parameters in @rv@ to match @src@,
+ *             and the size to @sz@.
+ */
+
+void tvec_initbuffer(union tvec_regval *rv,
+                    const union tvec_regval *src, size_t sz)
+  { rv->buf.sz = sz; rv->buf.a = src->buf.a; rv->buf.m = src->buf.m; }
+
+/* --- @tvec_allocbuffer@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *
+ * Returns:    ---
+ *
+ * Use:                Allocate @sz@ bytes to the buffer and fill the space with a
+ *             distinctive pattern.
+ */
+
+void tvec_allocbuffer(union tvec_regval *rv)
+{
+  unsigned char *p; size_t n;
+
+  if (rv->buf.p) xfree(rv->buf.p - rv->buf.off);
+
+  if (rv->buf.m < 2) {
+    rv->buf.p = xmalloc(rv->buf.sz); rv->buf.off = 0;
+  } else {
+    p = xmalloc(rv->buf.sz + rv->buf.m - 1);
+    n = (size_t)p%rv->buf.m;
+    rv->buf.off = (rv->buf.a - n + rv->buf.m)%rv->buf.m;
+    rv->buf.p = p + rv->buf.off;
+  }
+  memset(rv->buf.p, '?', rv->buf.sz);
+}
+
 /*----- That's all, folks -------------------------------------------------*/