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);
}
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);
}
* @const struct tvec_irange *ir@ = range specification,
* or null
* @struct tvec_state *tv@ = test vector state
+ * @const char *what@ = description of value
*
* Returns: Zero on success, or @-1@ on error.
*
static int check_signed_range(long i,
const struct tvec_irange *ir,
- struct tvec_state *tv)
+ struct tvec_state *tv, const char *what)
{
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);
+ tvec_error(tv, "%s %ld out of range (must be in [%ld .. %ld])",
+ what, i, ir->min, ir->max);
return (-1);
}
return (0);
static int check_unsigned_range(unsigned long u,
const struct tvec_urange *ur,
- struct tvec_state *tv)
+ struct tvec_state *tv, const char *what)
{
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);
+ tvec_error(tv, "%s %lu out of range (must be in [%lu .. %lu])",
+ what, u, ur->min, ur->max);
return (-1);
}
return (0);
if (parse_unsigned_integer(&u, &q, p))
return (tvec_error(tv, "invalid unsigned integer `%s'", p));
if (*q) return (tvec_syntax(tv, *q, "end-of-line"));
- if (check_unsigned_range(u, ur, tv)) return (-1);
+ if (check_unsigned_range(u, ur, tv, "integer")) return (-1);
*u_out = u; return (0);
}
if (parse_signed_integer(&i, &q, p))
return (tvec_error(tv, "invalid signed integer `%s'", p));
if (*q) return (tvec_syntax(tv, *q, "end-of-line"));
- if (check_signed_range(i, ir, tv)) return (-1);
+ if (check_signed_range(i, ir, tv, "integer")) return (-1);
*i_out = i; return (0);
}
+static const char size_units[] = "kMGTPEZY";
+
+/* --- @parse_szint@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @unsigned long *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_szint(struct tvec_state *tv, unsigned long *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);
+
+ for (t = u, unit = size_units; *unit; unit++) {
+ if (t > ULONG_MAX/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 (style&TVSF_RAW)
+ gprintf(gops, go, "%lu", u);
+ else 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 ------------------------------------------*/
* Arguments: @double x, y@ = two numbers to compare
* @const struct tvec_floatinfo *fi@ = floating-point info
*
- * Returns: Nonzero if the comparand @y@ is sufficiently close to the
- * reference @x@, or zero if it's definitely different.
+ * Returns: Nonzero if the comparand @x@ is sufficiently close to the
+ * reference @y@, or zero if it's definitely different.
*/
static int eqish_floating_p(double x, double y,
case TVFF_ABSDELTA:
t = x - y; if (t < 0) t = -t; return (t < fi->delta);
case TVFF_RELDELTA:
- t = 1.0 - y/x; if (t < 0) t = -t; return (t < fi->delta);
+ t = 1.0 - x/y; if (t < 0) t = -t; return (t < fi->delta);
default:
abort();
}
*
* 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,
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");
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");
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");
/* 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));
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 (STRCMP(w.buf, ==, "#empty")) break;
if (read_charname(&ch, w.buf, RCF_EOFOK)) {
rc = tvec_error(tv, "unknown character name `%s'", d.buf);
goto end;
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"))
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);
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);
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;
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;
unsigned style,
const struct gprintf_ops *gops, void *go)
{
-
+ if (style&TVSF_RAW) gprintf(gops, go, "int:");
gprintf(gops, go, "%ld", rv->i);
- if (!(style&TVSF_COMPACT)) {
+ if (!(style&(TVSF_COMPACT | TVSF_RAW))) {
gprintf(gops, go, " ; = ");
format_signed_hex(gops, go, rv->i);
maybe_format_signed_char(gops, go, rv->i);
unsigned style,
const struct gprintf_ops *gops, void *go)
{
+ if (style&TVSF_RAW) gprintf(gops, go, "uint:");
gprintf(gops, go, "%lu", rv->u);
- if (!(style&TVSF_COMPACT)) {
+ if (!(style&(TVSF_COMPACT | TVSF_RAW))) {
gprintf(gops, go, " ; = ");
format_unsigned_hex(gops, go, rv->u);
maybe_format_unsigned_char(gops, go, rv->u);
tvrange_size = { 0, (size_t)-1 },
tvrange_byte = { 0, 255 },
tvrange_u16 = { 0, 65535 },
- tvrange_u32 = { 0, 4294967296 };
+ tvrange_u32 = { 0, 4294967295 };
/* --- @tvec_claimeq_int@ --- *
*
return (tvec_claimeq(tv, &tvty_uint, 0, file, lno, expr));
}
+/*----- Size type ---------------------------------------------------------*/
+
+/* --- @parse_size@ --- *
+ *
+ * Arguments: @union tvec_regval *rv@ = register value
+ * @const struct tvec_regdef *rd@ = register definition
+ * @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns: Zero on success, %$-1$% on error.
+ *
+ * Use: Parse a register value from an input file.
+ *
+ * The input format for a size value consists of an unsigned
+ * integer followed by an optional unit specifier consisting of
+ * an SI unit prefix and (optionally) the letter `B'. */
+
+static int parse_size(union tvec_regval *rv, const struct tvec_regdef *rd,
+ struct tvec_state *tv)
+{
+ unsigned long sz;
+ int rc;
+
+ if (parse_szint(tv, &sz, ";", "size")) { rc = -1; goto end; }
+ if (check_unsigned_range(sz, rd->arg.p, tv, "size")) { rc = -1; goto end; }
+ if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
+ rv->u = sz; rc = 0;
+end:
+ return (rc);
+}
+
+/* --- @dump_size@ --- *
+ *
+ * Arguments: @const union tvec_regval *rv@ = register value
+ * @const struct tvec_regdef *rd@ = register definition
+ * @unsigned style@ = output style (@TVSF_...@)
+ * @const struct gprintf_ops *gops@, @void *gp@ = format output
+ *
+ * Returns: ---
+ *
+ * Use: Dump a register value to the format output.
+ *
+ * Size values are dumped with a unit specifier, with a unit
+ * prefox only if the size is an exact multiple of the relevant
+ * power of two. Unless compact style is requested, the plain
+ * decimal and hex representations of the value are also
+ * printed.
+ */
+
+static void dump_size(const union tvec_regval *rv,
+ const struct tvec_regdef *rd,
+ unsigned style,
+ const struct gprintf_ops *gops, void *go)
+{
+ if (style&TVSF_RAW) gprintf(gops, go, "size:");
+ format_size(gops, go, rv->u, style);
+ if (!(style&(TVSF_COMPACT | TVSF_RAW))) {
+ gprintf(gops, go, " ; = %lu", (unsigned long)rv->u);
+ gprintf(gops, go, " = "); format_unsigned_hex(gops, go, rv->u);
+ maybe_format_unsigned_char(gops, go, rv->u);
+ }
+}
+
+/* Size type definitions. */
+const struct tvec_regty tvty_size = {
+ init_uint, trivial_release, eq_uint,
+ tobuf_uint, frombuf_uint,
+ parse_size, dump_size
+};
+
+/* --- @tvec_claimeq_size@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @unsigned long sz0, sz1@ = two sizes
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @sz0@ and @sz1@ are equal, otherwise zero.
+ *
+ * Use: Check that values of @u0@ and @u1@ are equal. As for
+ * @tvec_claim@ above, a test case is automatically begun and
+ * ended if none is already underway. If the values are
+ * unequal, then @tvec_fail@ is called, quoting @expr@, and the
+ * mismatched values are dumped: @u0@ is printed as the output
+ * value and @u1@ is printed as the input reference.
+ */
+
+int tvec_claimeq_size(struct tvec_state *tv,
+ unsigned long sz0, unsigned long sz1,
+ const char *file, unsigned lno, const char *expr)
+{
+ tv->out[0].v.u = sz0; tv->in[0].v.u = sz1;
+ return (tvec_claimeq(tv, &tvty_size, 0, file, lno, expr));
+}
+
/*----- Floating-point type -----------------------------------------------*/
/* --- @int_float@ --- *
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; }
const struct tvec_regdef *rd,
unsigned style,
const struct gprintf_ops *gops, void *go)
- { format_floating(gops, go, rv->f); }
+{
+ if (style&TVSF_RAW) gprintf(gops, go, "float:");
+ format_floating(gops, go, rv->f);
+}
/* Floating-point type definition. */
const struct tvec_regty tvty_float = {
* @const char *file@, @unsigned @lno@ = calling file and line
* @const char *expr@ = the expression to quote on failure
*
- * Returns: Nonzero if @f0@ and @u1@ are sufficiently close, otherwise
+ * Returns: Nonzero if @f0@ and @f1@ are sufficiently close, otherwise
* zero.
*
* Use: Check that values of @f0@ and @f1@ are sufficiently close.
* %$y$% when %$|x - y| < \delta$%.
*
* * If @f&TVFF_EQMASK@ is @TVFF_RELDELTA@, then %$x$% matches
- * %$y$% when %$|1 - y/x| < \delta$%. (Note that this
- * criterion is asymmetric FIXME
+ * %$y$% when %$|1 - x/y| < \delta$%. (Note that this
+ * criterion is asymmetric. Write %$x \approx_\delta y$%
+ * if and only if %$|1 - x/y < \delta$%. Then, for example,
+ * if %$y/(1 + \delta) < x < y (1 - \delta)$%, then
+ * %$x \approx_\delta y$%, but %$y \not\approx_\delta x$%.)
*/
int tvec_claimeqish_float(struct tvec_state *tv,
* @const char *file@, @unsigned @lno@ = calling file and line
* @const char *expr@ = the expression to quote on failure
*
- * Returns: Nonzero if @f0@ and @u1@ are identical, otherwise zero.
+ * Returns: Nonzero if @f0@ and @f1@ are identical, otherwise zero.
*
* Use: Check that values of @f0@ and @f1@ are identical. The
* function is exactly equivalent to @tvec_claimeqish_float@
{ 0 }
};
+/* --- @tvec_parsedurunit@ --- *
+ *
+ * Arguments: @double *scale_out@ = where to leave the scale
+ * @const char **p_inout@ = input unit string, updated
+ *
+ * Returns: Zero on success, %$-1$% on error.
+ *
+ * Use: If @*p_inout@ begins with a unit string followed by the end
+ * of the string or some non-alphanumeric character, then store
+ * the corresponding scale factor in @*scale_out@, advance
+ * @*p_inout@ past the unit string, and return zero. Otherwise,
+ * return %$-1$%.
+ */
+
+int tvec_parsedurunit(double *scale_out, const char **p_inout)
+{
+ const char *p = *p_inout, *q;
+ const struct duration_unit *u;
+ size_t n;
+
+ while (ISSPACE(*p)) p++;
+ for (q = p; *q && ISALNUM(*q); q++);
+ n = q - p; if (!n) { *scale_out = 1.0; return (0); }
+
+ for (u = duration_units; u->unit; u++)
+ if (STRNCMP(p, ==, u->unit, n) && !u->unit[n])
+ { *scale_out = u->scale; *p_inout = q; return (0); }
+ return (-1);
+}
+
/* --- @parse_duration@ --- *
*
* Arguments: @union tvec_regval *rv@ = register value
{
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;
const struct duration_unit *u;
double t = rv->f;
- if (!t) u = 0;
- else {
- for (u = duration_units; u->scale > t && u[1].unit; u++);
- t /= u->scale;
- }
-
- gprintf(gops, go, "%.4g %s", t, u ? u->unit : "s");
- if (!(style&TVSF_COMPACT)) {
- gprintf(gops, go, "; = ");
+ if (style&TVSF_RAW) {
+ gprintf(gops, go, "duration:");
format_floating(gops, go, rv->f);
- gprintf(gops, go, " s");
+ gprintf(gops, go, "s");
+ } else {
+ if (!t) u = 0;
+ else {
+ for (u = duration_units; u->scale > t && u[1].unit; u++);
+ t /= u->scale;
+ }
+ gprintf(gops, go, "%.4g %s", t, u ? u->unit : "s");
+
+ if (!(style&TVSF_COMPACT)) {
+ gprintf(gops, go, "; = ");
+ format_floating(gops, go, rv->f);
+ gprintf(gops, go, " s");
+ }
}
}
parse_duration, dump_duration
};
+/* --- @tvec_claimeqish_duration@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @double to, t1@ = two durations
+ * @unsigned f@ = flags (@TVFF_...@)
+ * @double delta@ = maximum tolerable difference
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @t0@ and @t1@ are sufficiently close, otherwise
+ * zero.
+ *
+ * Use: Check that values of @t0@ and @t1@ are sufficiently close.
+ * This is essentially the same as @tvec_claimeqish_float@, only
+ * it dumps the values as durations on a mismatch.
+ */
+
+int tvec_claimeqish_duration(struct tvec_state *tv,
+ double t0, double t1, unsigned f, double delta,
+ const char *file, unsigned lno,
+ const char *expr)
+{
+ struct tvec_floatinfo fi;
+ union tvec_misc arg;
+
+ fi.f = f; fi.min = fi.max = 0.0; fi.delta = delta; arg.p = &fi;
+ tv->out[0].v.f = t0; tv->in[0].v.f = t1;
+ return (tvec_claimeq(tv, &tvty_duration, &arg, file, lno, expr));
+}
+
+/* --- @tvec_claimeq_duration@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @double t0, t1@ = two durations
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @t0@ and @t1@ are identical, otherwise zero.
+ *
+ * Use: Check that values of @t0@ and @t1@ are identical. The
+ * function is exactly equivalent to @tvec_claimeqish_duration@
+ * with @f == TVFF_EXACT@.
+ */
+
+int tvec_claimeq_duration(struct tvec_state *tv,
+ double t0, double t1,
+ const char *file, unsigned lno,
+ const char *expr)
+{
+ return (tvec_claimeqish_duration(tv, t0, t1, TVFF_EXACT, 0.0,
+ file, lno, expr));
+}
+
/*----- Enumerations ------------------------------------------------------*/
/* --- @init_tenum@ --- *
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);
}
dstr d = DSTR_INIT; \
int rc; \
\
- if (tvec_readword(tv, &d, ";", "enumeration tag or " LITSTR_##tag_)) \
+ if (tvec_readword(tv, &d, 0, \
+ ";", "%s tag or " LITSTR_##tag_, ei->name)) \
{ rc = -1; goto end; } \
for (a = ei->av; a->tag; a++) \
if (STRCMP(a->tag, ==, d.buf)) { FOUND_##tag_ goto done; } \
const struct tvec_##slot##enuminfo *ei = rd->arg.p; \
const struct tvec_##slot##assoc *a; \
\
+ if (style&TVSF_RAW) gprintf(gops, go, #slot "enum/%s:", ei->name); \
for (a = ei->av; a->tag; a++) \
if (rv->slot == a->slot) { \
gprintf(gops, go, "%s", a->tag); \
/* Read the next item. */
DRESET(&d);
- if (tvec_readword(tv, &d, "|;", "flag name or integer"))
+ if (tvec_readword(tv, &d, 0, "|;", "%s flag name or integer", fi->name))
{ rc = -1; goto end; }
/* Try to find a matching entry in the table. */
if (tvec_nexttoken(tv)) break;
ch = getc(tv->fp);
if (ch != '|') { tvec_syntax(tv, ch, "`|'"); rc = -1; goto end; }
- if (tvec_nexttoken(tv))
- { tvec_syntax(tv, '\n', "flag name or integer"); rc = -1; goto end; }
+ if (tvec_nexttoken(tv)) {
+ tvec_syntax(tv, '\n', "%s flag name or integer", fi->name);
+ rc = -1; goto end;
+ }
}
/* Done. */
unsigned long m = ~0ul, v = rv->u;
const char *sep;
+ if (style&TVSF_RAW) gprintf(gops, go, "flags/%s:", fi->name);
+
for (f = fi->fv, sep = ""; f->tag; f++)
if ((m&f->m) && (v&f->m) == f->v) {
gprintf(gops, go, "%s%s", sep, f->tag); m &= ~f->m;
if (v&m) gprintf(gops, go, "%s0x%0*lx", sep, hex_width(v), v&m);
- if (m != ~0ul && !(style&TVSF_COMPACT))
+ if (m != ~0ul && !(style&(TVSF_COMPACT | TVSF_RAW)))
gprintf(gops, go, " ; = 0x%0*lx", hex_width(rv->u), rv->u);
}
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));
}
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);
}
*/
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);
unsigned f = 0;
#define f_semi 1u
- /* Print a character name if we can find one. */
- p = find_charname(rv->i, (style&TVSF_COMPACT) ? CTF_SHORT : CTF_PREFER);
- if (p) {
- gprintf(gops, go, "%s", p);
- if (style&TVSF_COMPACT) return;
- else { gprintf(gops, go, " ;"); f |= f_semi; }
- }
+ if (style&TVSF_RAW) {
+ /* Print the raw character unconditionally in single quotes. */
- /* If the character isn't @EOF@ then print it as a single-quoted thing.
- * In compact style, see if we can omit the quotes.
- */
- if (rv->i >= 0) {
- if (f&f_semi) gprintf(gops, go, " = ");
- switch (rv->i) {
- case ' ': case '\\': case '\'': quote:
- format_char(gops, go, rv->i);
- break;
- default:
- if (!(style&TVSF_COMPACT) || !isprint(rv->i)) goto quote;
- gprintf(gops, go, "%c", (int)rv->i);
- return;
+ gprintf(gops, go, "char:'");
+ format_char(gops, go, rv->i);
+ gprintf(gops, go, "'");
+ } else {
+ /* Print ina pleasant human-readable way. */
+
+ /* Print a character name if we can find one. */
+ p = find_charname(rv->i, (style&TVSF_COMPACT) ? CTF_SHORT : CTF_PREFER);
+ if (p) {
+ gprintf(gops, go, "%s", p);
+ if (style&TVSF_COMPACT) return;
+ else { gprintf(gops, go, " ;"); f |= f_semi; }
}
- }
- /* And the character code as an integer. */
- if (!(style&TVSF_COMPACT)) {
- if (!(f&f_semi)) gprintf(gops, go, " ;");
- gprintf(gops, go, " = %ld = ", rv->i);
- format_signed_hex(gops, go, rv->i);
+ /* If the character isn't @EOF@ then print it as a single-quoted thing.
+ * In compact style, see if we can omit the quotes.
+ */
+ if (rv->i >= 0) {
+ if (f&f_semi) gprintf(gops, go, " = ");
+ switch (rv->i) {
+ case ' ': case '\\': case '\'': quote:
+ format_char(gops, go, rv->i);
+ break;
+ default:
+ if (!(style&TVSF_COMPACT) || !isprint(rv->i)) goto quote;
+ gprintf(gops, go, "%c", (int)rv->i);
+ return;
+ }
+ }
+
+ /* And the character code as an integer. */
+ if (!(style&TVSF_COMPACT)) {
+ if (!(f&f_semi)) gprintf(gops, go, " ;");
+ gprintf(gops, go, " = %ld = ", rv->i);
+ format_signed_hex(gops, go, rv->i);
+ }
}
#undef f_semi
* groups of hex digits, with comments showing the offset (if
* the string is long enough) and the corresponding plain text.
*
- * Empty strings are dumped as %|""|%.
+ * Empty strings are dumped as %|#empty|%.
*/
+static void dump_empty(const char *ty, unsigned style,
+ const struct gprintf_ops *gops, void *go)
+{
+ if (style&TVSF_RAW) gprintf(gops, go, "%s:", ty);
+ if (!(style&TVSF_COMPACT)) gprintf(gops, go, "#empty");
+ if (!(style&(TVSF_COMPACT | TVSF_RAW))) gprintf(gops, go, " ; = ");
+ if (!(style&TVSF_RAW)) gprintf(gops, go, "\"\"");
+}
+
+
static void dump_text(const union tvec_regval *rv,
const struct tvec_regdef *rd,
unsigned style,
#define f_nonword 1u
#define f_newline 2u
- if (!rv->text.sz) { gprintf(gops, go, "\"\""); return; }
+ if (!rv->text.sz) { dump_empty("text", style, gops, go); return; }
p = (const unsigned char *)rv->text.p; l = p + rv->text.sz;
+ if (style&TVSF_RAW) { gprintf(gops, go, "text:"); goto quote; }
+ else if (style&TVSF_COMPACT) goto quote;
+
switch (*p) {
case '!': case '#': case ';': case '"': case '\'':
case '(': case '{': case '[': case ']': case '}': case ')':
}
for (q = p; q < l; q++)
if (*q == '\n' && q != l - 1) f |= f_newline;
- else if (!*q || !isgraph(*q) || *q == '\\') f |= f_nonword;
+ else if (!*q || !ISGRAPH(*q) || *q == '\\') f |= f_nonword;
if (f&f_newline) { gprintf(gops, go, "\n\t"); goto quote; }
else if (f&f_nonword) goto quote;
quote:
gprintf(gops, go, "\"");
for (q = p; q < l; q++)
- if (!isprint(*q) || *q == '"') {
+ if (!ISPRINT(*q) || *q == '"') {
if (p < q) gops->putm(go, (const char *)p, q - p);
if (*q != '\n' || (style&TVSF_COMPACT))
format_charesc(gops, go, *q, FCF_BRACE);
unsigned i, n;
int wd;
- if (!sz) {
- gprintf(gops, go, style&TVSF_COMPACT ? "\"\"" : "\"\" ; empty");
- return;
- }
+ if (!rv->text.sz) { dump_empty("bytes", style, gops, go); return; }
- if (style&TVSF_COMPACT) {
+ if (style&(TVSF_COMPACT | TVSF_RAW)) {
while (p < l) gprintf(gops, go, "%02x", *p++);
return;
}
/*----- 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@ --- *
*
*
* 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@ --- *
*
*
* 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@ --- *
*
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);
}
*
* Use: Parse a register value from an input file.
*
- * The input format for a buffer value consists of an unsigned
- * integer followed by an optional unit specifier consisting of
- * an SI unit prefix and (optionally) the letter `B'. Unit
- * prefixes denote %%\emph{binary}%% multipliers, not decimal.
+ * The input format for a buffer value is a size, followed by an
+ * optional `%|@$%' and an alignment quantum and a further
+ * optional `%|+|%' and an alignment offset. The size, quantum,
+ * and offset are syntactically sizes.
*
- * The buffer is allocated and filled with a distinctive
- * pattern.
+ * The buffer is not allocated.
*/
-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_szint(tv, &sz, "@;", "buffer length")) { rc = -1; goto end; }
+ if (check_unsigned_range(sz, &tvrange_size, tv, "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_szint(tv, &m, "+;", "alignment quantum")) { rc = -1; goto end; }
+ if (check_unsigned_range(a, &tvrange_size, tv, "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_szint(tv, &a, ";", "alignment offset")) { rc = -1; goto end; }
+ if (check_unsigned_range(m, &tvrange_size, tv, "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@ --- *
*
* Use: Dump a register value to the format output.
*
- * Buffer values are dumped as their size with an appropriate
- * unit specifier. A unit prefix is only used if the size is an
- * exact multiple of the relevant power of two.
+ * Buffer values are dumped as their size, with the alignment
+ * quantum and alignment offset if these are non-default.
*/
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 | TVSF_RAW) ? "@" : " @ ");
+ format_size(gops, go, rv->buf.m, style);
+ if (rv->buf.a) {
+ gprintf(gops, go, style&(TVSF_COMPACT | TVSF_RAW) ? "+" : " + ");
+ format_size(gops, go, rv->buf.a, style);
+ }
+ }
+ if (!(style&TVSF_COMPACT)) {
+ gprintf(gops, go, " ; = %lu", (unsigned long)rv->buf.sz);
+ if (rv->buf.m) {
+ gprintf(gops, go, " @ %lu", (unsigned long)rv->buf.m);
+ if (rv->buf.a) gprintf(gops, go, " + %lu", (unsigned long)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 *ref@ = source buffer
+ * @size_t sz@ = size to allocate
+ *
+ * Returns: ---
+ *
+ * Use: Initialize the alignment parameters in @rv@ to match @ref@,
+ * and the size to @sz@.
+ */
+
+void tvec_initbuffer(union tvec_regval *rv,
+ const union tvec_regval *ref, size_t sz)
+ { rv->buf.sz = sz; rv->buf.a = ref->buf.a; rv->buf.m = ref->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 -------------------------------------------------*/