case PC: \
if (f & CDCF_IGNEQMID) break; \
if (f & CDCF_NOEQPAD) goto badch; \
- if (st == ST_MAIN && \
- !(f & CDCF_IGNZPAD) && (a & ((1 << nb) - 1))) \
+ if (st == ST_MAIN && !(f & CDCF_IGNZPAD) && \
+ ((nb && !(nb%wd)) || (a & ((1 << nb) - 1)))) \
return (CDCERR_INVZPAD); \
st = ST_PAD; \
if (!(f & CDCF_IGNEQPAD)) { \
} \
} \
} else { \
- if (st == ST_MAIN && \
- !(f & CDCF_IGNZPAD) && (a & ((1 << nb) - 1))) \
+ if (st == ST_MAIN && !(f & CDCF_IGNZPAD) && \
+ ((nb && !(nb%wd)) || (a & ((1 << nb) - 1)))) \
return (CDCERR_INVZPAD); \
if (!(f & (CDCF_IGNEQPAD | CDCF_IGNEQMID | CDCF_NOEQPAD)) && nb) \
return (CDCERR_INVEQPAD); \
_(OK, "No error") \
_(INVCH, "Invalid character") \
_(INVEQPAD, "Invalid padding character") \
- _(INVZPAD, "Nonzero padding bits")
+ _(INVZPAD, "Excess or nonzero padding bits")
enum {
#define DECLERR(name, text) CDCERR_##name,
CODEC_ERRORS(DECLERR)
[1], [Invalid padding character])
## Test for incorrect padding bits.
+CODEC_TESTDECODE([base64], [A===], [], [],
+ [1], [Excess or nonzero padding bits])
CODEC_TESTDECODE([base64], [Zh==], [], [],
- [1], [Nonzero padding bits])
+ [1], [Excess or nonzero padding bits])
CODEC_TESTDECODE([base64], [Zh==], [f], [-fignzpad])
## Make sure the case flags are suppressed.
#include "bits.h"
#include "buf.h"
+#include "maths.h"
/*----- Formatting primitives ---------------------------------------------*/
/* Some machinery before we start. */
-#ifdef isnan
-# define NANP(x) isnan(x)
-#else
-# define NANP(x) (!((x) == (x)))
-#endif
-
-#ifdef isinf
-# define INFP(x) isinf(x)
-#else
-# define INFP(x) ((x) > DBL_MAX || (x) < -DBL_MAX)
-#endif
-
-#ifdef signbit
-# define NEGP(x) signbit(x)
-#else
-# define NEGP(x) ((x) < 0) /* incorrect for negative zero! */
-#endif
-
if (NANP(x)) {
/* A NaN. */
hi = 0x7ff80000; lo = 0;
/* Convert to external format and go home. */
SET64(k, hi, lo); return (k);
-
-#undef NANP
-#undef INFP
-#undef NEGP
}
/* --- @k64_to_f64@ --- *
/*----- Header files ------------------------------------------------------*/
+#include "config.h"
+
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
va_end(ap); b->p += n; return (n);
}
-static int putbuf(void *out, const char *p, size_t sz)
- { buf *b = out; b->p += sz; return (0); }
-
const struct gprintf_ops buf_printops =
{ putch, putm, nputf };
/*----- Header files ------------------------------------------------------*/
+#include "config.h"
+
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
struct timer {
struct bench_timer _t;
- const struct timer_ops *clkops, *cyops;
- union { int fd; } u_cy;
+ const struct timer_ops *clkops, *cyops; /* time and cycle measurements */
+ union { int fd; } u_cy; /* state for cycle measurement */
};
struct timer_ops {
- void (*now)(struct bench_time *t_out, struct timer *t);
- void (*teardown)(struct timer *t);
+ void (*now)(struct bench_time *t_out, struct timer *t); /* read current */
+ void (*teardown)(struct timer *t); /* release held resources */
};
/*----- Preliminaries -----------------------------------------------------*/
#define NS_PER_S 1000000000
+/* --- @debug@ --- *
+ *
+ * Arguments: @const char *fmt@ = format control string
+ * @...@ = format arguemnts
+ *
+ * Returns: ---
+ *
+ * Use: Maybe report a debugging message to standard error.
+ */
+
static void PRINTF_LIKE(1, 2) debug(const char *fmt, ...)
{
const char *p;
}
}
+/* --- @timer_diff@ --- *
+ *
+ * Arguments: @struct bench_timing *delta_out@ = where to putt the result
+ * @const struct bench_time *t0, *t1@ = two times captured by a
+ * timer's @now@ function
+ *
+ * Returns: ---
+ *
+ * Use: Calculates the difference between two captured times. The
+ * flags are set according to whether the differences are
+ * meaningful; @delta_out->n@ is left unset.
+ */
+
+static void timer_diff(struct bench_timing *delta_out,
+ const struct bench_time *t0,
+ const struct bench_time *t1)
+{
+ unsigned f = t0->f&t1->f;
+ kludge64 k;
+
+#ifdef HAVE_UINT64
+# define FLOATK64(k) ((double)(k).i)
+#else
+# define FLOATK64(k) ((double)(k).lo + 4275123318.0*(double)(k).hi)
+#endif
+
+ if (!(f&BTF_TIMEOK))
+ delta_out->t = 0.0;
+ else {
+ SUB64(k, t1->s, t0->s);
+ delta_out->t = FLOATK64(k) - 1 +
+ (t1->ns + NS_PER_S - t0->ns)/(double)NS_PER_S;
+ }
+
+ if (!(f&BTF_CYOK))
+ delta_out->cy = 0.0;
+ else {
+ SUB64(k, t1->cy, t0->cy);
+ delta_out->cy = FLOATK64(k);
+ }
+
+ delta_out->f = f;
+
+#undef FLOATK64
+}
+
/*----- The null clock ----------------------------------------------------*/
+/* This is a cycle counter which does nothing, in case we don't have any
+ * better ideas.
+ */
+
static void null_now(struct bench_time *t_out, struct timer *t) { ; }
static void null_teardown(struct timer *t) { ; }
static const struct timer_ops null_ops = { null_now, null_teardown };
/*----- Linux performance counters ----------------------------------------*/
+/* This is a cycle counter which uses the Linux performance event system,
+ * which is probably the best choice if it's available.
+ */
+
#if defined(HAVE_LINUX_PERF_EVENT_H) && defined(HAVE_UINT64)
#include <sys/types.h>
/*----- Intel time-stamp counter ------------------------------------------*/
+/* This is a cycle counter based on the Intel `rdtsc' instruction. It's not
+ * really suitable for performance measurement because it gets confused by
+ * CPU frequency adjustments.
+ */
+
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
#define EFLAGS_ID (1u << 21)
/*----- POSIX `clock_gettime' ---------------------------------------------*/
+/* This is a real-time clock based on the POSIX time interface, with up to
+ * nanosecond precision.
+ */
+
#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_THREAD_CPUTIME_ID)
static void gettime_now(struct bench_time *t_out, struct timer *t)
/*----- Standard C `clock' ------------------------------------------------*/
+/* This is a real-time clock based on the C `clock' function which is
+ * guaranteed to be available, though it's not likely to be very good.
+ */
+
static void clock_now(struct bench_time *t_out, struct timer *t)
{
clock_t now, x;
/*----- Timing setup ------------------------------------------------------*/
+/* Tables of timing sources. */
static const struct timerent {
const char *name;
int (*init)(struct timer */*t*/);
clktab[] = { GETTIME_CLKENT CLOCK_CLKENT { 0, 0 } },
cytab[] = { PERFEVENT_CYENT X86RDTSC_CYENT NULL_CYENT { 0, 0 } };
+/* --- @find_timer@ --- *
+ *
+ * Arguments: @const char *name@ = timer name
+ * @size_t sz@ = length of name
+ * @const struct timerent *timers@ = table to search
+ * @const char *what@ = adjective describing table
+ *
+ * Returns: The table entry matching the given name, or null if there
+ * isn't one.
+ */
+
static const struct timerent *find_timer_n(const char *name, size_t sz,
const struct timerent *timers,
const char *what)
debug("%s timer `%.*s' not found", what, (int)sz, name); return (0);
}
+/* --- @try_timer@ --- *
+ *
+ * Arguments: @struct timer *t@ = timer structure
+ * @const struct timerent *timer@ = timer table entry
+ * @const char *what@ = adjective describing table
+ *
+ * Returns: Zero on success, @-1@ if timer failed.
+ *
+ * Use: Tries to initialize the timer @t@, reporting a debug message
+ * if it worked.
+ */
+
static int try_timer(struct timer *t,
const struct timerent *timer, const char *what)
{
debug("selected %s timer `%s'", what, timer->name); return (0);
}
+/* --- @select_timer@ --- *
+ *
+ * Arguments: @struct timer *t@ = timer structure
+ * @const struct timerent *timer@ = timer table
+ * @const char *varname@ = environment variable to consult
+ * @const char *what@ = adjective describing table
+ *
+ * Returns: Zero on success, @-1@ if timer failed.
+ *
+ * Use: Select a timer from the table. If the environment variable
+ * is set, then parse a comma-separated list of timer names and
+ * use the first one listed that seems to work; otherwise, try
+ * the timers in the table in order.
+ */
+
static int select_timer(struct timer *t, const struct timerent *timers,
const char *varname, const char *what)
{
debug("no suitable %s timer found", what); return (-1);
}
+/* Bench timer operations. */
static void timer_now(struct bench_timer *tm, struct bench_time *t_out)
{
struct timer *t = (struct timer *)tm;
t->clkops->now(t_out, t);
t->cyops->now(t_out, t);
}
-
static void timer_destroy(struct bench_timer *tm)
{
struct timer *t = (struct timer *)tm;
static const struct bench_timerops timer_ops = { timer_now, timer_destroy };
+/* --- @bench_createtimer@ --- *
+ *
+ * Arguments: ---
+ *
+ * Returns: A freshly constructed standard timer object.
+ *
+ * Use: Allocate a timer. Dispose of it by calling
+ * @tm->ops->destroy(tm)@ when you're done.
+ */
+
struct bench_timer *bench_createtimer(void)
{
struct timer *t = 0;
return (ret);
}
-#ifdef HAVE_UINT64
-# define FLOATK64(k) ((double)(k).i)
-#else
-# define FLOATK64(k) ((double)(k).lo + 4275123318.0*(double)(k).hi)
-#endif
-
-static void timer_diff(struct bench_timing *delta_out,
- const struct bench_time *t0,
- const struct bench_time *t1)
-{
- delta_out->f = t0->f&t1->f;
- kludge64 k;
-
- if (!(delta_out->f&BTF_TIMEOK))
- delta_out->t = 0.0;
- else {
- SUB64(k, t1->s, t0->s);
- delta_out->t = FLOATK64(k) - 1 +
- (t1->ns + NS_PER_S - t0->ns)/(double)NS_PER_S;
- }
-
- if (!(delta_out->f&BTF_CYOK))
- delta_out->cy = 0.0;
- else {
- SUB64(k, t1->cy, t0->cy);
- delta_out->cy = FLOATK64(k);
- }
-}
+/*----- Benchmarking ------------------------------------------------------*/
-/*----- Calibration -------------------------------------------------------*/
+/* --- @bench_init@ --- *
+ *
+ * Arguments: @struct bench_state *b@ = bench state to initialize
+ * @struct bench_timer *tm@ = timer to attach
+ *
+ * Returns: ---
+ *
+ * Use: Initialize the benchmark state. It still needs to be
+ * calibrated (use @bench_calibrate@) before it can be used, but
+ * this will be done automatically by @bench_measure@ if it's
+ * not done by hand earlier. The timer is now owned by the
+ * benchmark state and will be destroyed by @bench_destroy@.
+ */
void bench_init(struct bench_state *b, struct bench_timer *tm)
{ b->tm = tm; b->target_s = 1.0; b->f = 0; }
+/* --- @bench_destroy@ --- *
+ *
+ * Arguments: @struct bench_state *b@ = bench state
+ *
+ * Returns: ---
+ *
+ * Use: Destroy the benchmark state, releasing the resources that it
+ * holds.
+ */
+
void bench_destroy(struct bench_state *b)
{ b->tm->ops->destroy(b->tm); }
-static void do_nothing(unsigned long n, void *p)
+/* --- @do_nothing@ --- *
+ *
+ * Arguments: @unsigned long n@ = iteration count
+ * @void *ctx@ = context pointer (ignored)
+ *
+ * Returns: ---
+ *
+ * Use: Does nothing at all for @n@ iterations. Used to calibrate
+ * the benchmarking state.
+ */
+
+static void do_nothing(unsigned long n, void *ctx)
{ while (n--) RELAX; }
+/* --- @bench_calibrate@ --- *
+ *
+ * Arguments: @struct bench_state *b@ = bench state
+ *
+ * Returns: Zero on success, @-1@ if calibration failed.
+ *
+ * Use: Calibrate the benchmark state, so that it can be used to
+ * measure performance reasonably accurately.
+ */
+
int bench_calibrate(struct bench_state *b)
{
struct linreg lr_clk = LINREG_INIT, lr_cy = LINREG_INIT;
unsigned f = BTF_ANY;
int rc;
+ /* The model here is that a timing loop has a fixed overhead as we enter
+ * and leave (e.g., to do with the indirect branch into the code), and
+ * per-iteration overheads as we check the counter and loop back. We aim
+ * to split these apart using linear regression.
+ */
+
+ /* If we've already calibrated then there's nothing to do. */
if (b->f&BTF_ANY) return (0);
+ /* Exercise the inner loop a few times to educate the branch predictor. */
for (i = 0; i < 10; i++)
- { tm->ops->now(tm, &t0); fn(1, 0); tm->ops->now(tm, &t1); }
+ { tm->ops->now(tm, &t0); fn(50, 0); tm->ops->now(tm, &t1); }
+ /* Now we measure idle loops until they take sufficiently long -- or we run
+ * out of counter.
+ */
debug("calibrating...");
n = 1;
for (;;) {
+
+ /* Measure @n@ iterations of the idle loop. */
tm->ops->now(tm, &t0); fn(n, 0); tm->ops->now(tm, &t1);
timer_diff(&delta, &t0, &t1); f &= delta.f;
if (!(f&BTF_TIMEOK)) { rc = -1; goto end; }
+
+ /* Register the timings with the regression machinery. */
linreg_update(&lr_clk, n, delta.t);
if (!(f&BTF_CYOK))
debug(" n = %10lu; t = %12g s", n, delta.t);
linreg_update(&lr_cy, n, delta.cy);
debug(" n = %10lu; t = %12g s, cy = %10.0f", n, delta.t, delta.cy);
}
+
+ /* If we're done then stop. */
if (delta.t >= b->target_s/20.0) break;
if (n >= ULONG_MAX - n/3) break;
+
+ /* Update the counter and continue. */
n += n/3 + 1;
}
+ /* Now run the linear regression to extract the constant and per-iteration
+ * overheads.
+ */
linreg_fit(&lr_clk, &b->clk.m, &b->clk.c, 0);
debug("clock overhead = (%g n + %g) s", b->clk.m, b->clk.c);
if (f&BTF_CYOK) {
linreg_fit(&lr_clk, &b->clk.m, &b->clk.c, 0);
debug("cycle overhead = (%g n + %g) cy", b->cy.m, b->cy.c);
}
+
+ /* We're done. */
b->f |= f; rc = 0;
end:
return (rc);
}
+/* --- @bench_measure@ --- *
+ *
+ * Arguments: @struct bench_timing *t_out@ = where to leave the timing
+ * @struct bench_state *b@ = benchmark state
+ * @double base@ = number of internal units per call
+ * @bench_fn *fn@, @void *ctx@ = benchmark function to run
+ *
+ * Returns: Zero on success, @-1@ if timing failed.
+ *
+ * Use: Measure a function. The function @fn@ is called adaptively
+ * with an iteration count @n@ set so as to run for
+ * approximately @b->target_s@ seconds.
+ *
+ * The result is left in @*t_out@, with @t_out->n@ counting the
+ * final product of the iteration count and @base@ (which might,
+ * e.g., reflect the number of inner iterations the function
+ * performs, or the number of bytes it processes per iteration).
+ */
+
int bench_measure(struct bench_timing *t_out, struct bench_state *b,
- double base, bench_fn *fn, void *p)
+ double base, bench_fn *fn, void *ctx)
{
struct bench_timer *tm = b->tm;
struct bench_time t0, t1;
unsigned long n;
+ /* Make sure the state is calibrated. */
if (bench_calibrate(b)) return (-1);
+
+ /* Main adaptive measurement loop. */
debug("measuring..."); n = 1;
for (;;) {
- tm->ops->now(tm, &t0); fn(n, p); tm->ops->now(tm, &t1);
+ tm->ops->now(tm, &t0); fn(n, ctx); tm->ops->now(tm, &t1);
timer_diff(t_out, &t0, &t1);
if (!(t_out->f&BTF_TIMEOK)) return (-1);
if (!(t_out->f&BTF_CYOK)) debug(" n = %10lu; t = %12g", n, t_out->t);
if (t_out->t >= 0.72*b->target_s) break;
n *= 1.44*b->target_s/t_out->t;
}
+
+ /* Adjust according to the calibration. */
t_out->t -= n*b->clk.m + b->clk.c;
if (t_out->f&BTF_CYOK) t_out->cy -= n*b->cy.m + b->cy.c;
+
+ /* Report the results, if debugging. */
if (!(t_out->f&BTF_CYOK)) debug(" adjusted t' = %12g", t_out->t);
else debug(" adjusted t = %12g, cy = %10.0f", t_out->t, t_out->cy);
if (!(t_out->f&BTF_CYOK))
else
debug(" %g s (%g cy) per op; %g ops/s",
t_out->t/n, t_out->cy/n, n/t_out->t);
+
+ /* All done. */
t_out->n = n*base; return (0);
}
/*----- Data structures ---------------------------------------------------*/
struct bench_time {
- unsigned f;
-#define BTF_TIMEOK 1u
-#define BTF_CYOK 2u
-#define BTF_ANY (BTF_TIMEOK | BTF_CYOK)
- kludge64 s; uint32 ns;
- kludge64 cy;
+ unsigned f; /* flags */
+#define BTF_TIMEOK 1u /* @s@ ad @ns@ slots are value */
+#define BTF_CYOK 2u /* @cy@ slot is valid */
+#define BTF_ANY (BTF_TIMEOK | BTF_CYOK) /* some part is useful */
+ kludge64 s; uint32 ns; /* real time in seconds and nanos */
+ kludge64 cy; /* count of CPU cycles */
};
struct bench_timing {
- unsigned f;
- double n, t, cy;
+ unsigned f; /* flags (as in @struct bench_time@) */
+ double n, t, cy; /* count, time, and cycles */
};
struct bench_timer { const struct bench_timerops *ops; };
struct bench_timerops {
void (*now)(struct bench_timer */*bt*/, struct bench_time */*t_out*/);
+ /* Fill in @*t_out@ with the current time. v*/
+
void (*destroy)(struct bench_timer */*bt*/);
+ /* Release the timer and any resources it holds. */
};
struct bench_state {
- struct bench_timer *tm;
- double target_s;
- unsigned f;
- struct { double m, c; } clk, cy;
+ struct bench_timer *tm; /* a timer */
+ double target_s; /* target time to run benchmarks */
+ unsigned f; /* flags (@BTF_...@) for calibrations */
+ struct { double m, c; } clk, cy; /* calculated overheads */
};
-typedef void bench_fn(unsigned long /*n*/, void */*p*/);
+typedef void bench_fn(unsigned long /*n*/, void */*ctx*/);
+/* Run the benchmark @n@ times, given a context pointer @ctx@. */
/*----- Functions provided ------------------------------------------------*/
+/* --- @bench_createtimer@ --- *
+ *
+ * Arguments: ---
+ *
+ * Returns: A freshly constructed standard timer object.
+ *
+ * Use: Allocate a timer. Dispose of it by calling
+ * @tm->ops->destroy(tm)@ when you're done.
+ */
+
extern struct bench_timer *bench_createtimer(void);
+/* --- @bench_init@ --- *
+ *
+ * Arguments: @struct bench_state *b@ = bench state to initialize
+ * @struct bench_timer *tm@ = timer to attach
+ *
+ * Returns: ---
+ *
+ * Use: Initialize the benchmark state. It still needs to be
+ * calibrated (use @bench_calibrate@) before it can be used, but
+ * this will be done automatically by @bench_measure@ if it's
+ * not done by hand earlier. The timer is now owned by the
+ * benchmark state and will be destroyed by @bench_destroy@.
+ */
+
extern void bench_init(struct bench_state */*b*/,
struct bench_timer */*tm*/);
+/* --- @bench_destroy@ --- *
+ *
+ * Arguments: @struct bench_state *b@ = bench state
+ *
+ * Returns: ---
+ *
+ * Use: Destroy the benchmark state, releasing the resources that it
+ * holds.
+ */
+
extern void bench_destroy(struct bench_state */*b*/);
+/* --- @bench_calibrate@ --- *
+ *
+ * Arguments: @struct bench_state *b@ = bench state
+ *
+ * Returns: Zero on success, @-1@ if calibration failed.
+ *
+ * Use: Calibrate the benchmark state, so that it can be used to
+ * measure performance reasonably accurately.
+ */
+
extern int bench_calibrate(struct bench_state */*b*/);
+/* --- @bench_measure@ --- *
+ *
+ * Arguments: @struct bench_timing *t_out@ = where to leave the timing
+ * @struct bench_state *b@ = benchmark state
+ * @double base@ = number of internal units per call
+ * @bench_fn *fn@, @void *ctx@ = benchmark function to run
+ *
+ * Returns: Zero on success, @-1@ if timing failed.
+ *
+ * Use: Measure a function. The function @fn@ is called adaptively
+ * with an iteration count @n@ set so as to run for
+ * approximately @b->target_s@ seconds.
+ *
+ * The result is left in @*t_out@, with @t_out->n@ counting the
+ * final product of the iteration count and @base@ (which might,
+ * e.g., reflect the number of inner iterations the function
+ * performs, or the number of bytes it processes per iteration).
+ */
+
extern int bench_measure(struct bench_timing */*t_out*/,
struct bench_state */*b*/,
- double /*base*/, bench_fn */*fn*/, void */*p*/);
+ double /*base*/, bench_fn */*fn*/, void */*ctx*/);
/*----- That's all, folks -------------------------------------------------*/
_(flags, RF, flags, p, &attr_info) \
_(string, RSTR, string, p, &range_32) \
_(bytes, RBY, bytes, p, &tvrange_byte) \
- _(buffer, RBUF, buffer, p, &tvrange_u16)
+ _(buffer, RBUF, buffer, p, 0)
enum {
/* Output registers, one for each register type. */
test_parse([int], [ 17; comment], [17 ; = 0x11 = '\x11'])
test_parse([int], [0x234], [564 ; = 0x0234])
-test_parse([int], [0o33], [27 ; = 0x1b = '\e'])
+test_parse([int], [0o33], [27 ; = 0x1b = @%:@escape = '\e'])
test_parse([int], [ +192], [192 ; = 0xc0 = '\xc0'])
test_parse([int], [ -192], [-192 ; = -0xc0])
[3], [syntax error: expected end-of-line but found `x'])
test_parserr([int], [],
- [3], [syntax error: expected signed integer but found @%:@<eol>])
+ [3], [syntax error: expected signed integer but found @%:@eol])
test_parserr([int], [123456],
[3], [integer 123456 out of range (must be in @<:@-32768 .. 32767@:>@)])
test_parse([uint], [4], [4 ; = 0x04 = '\x04'])
test_parse([uint], [ 17; comment], [17 ; = 0x11 = '\x11'])
+test_parse([uint], [0], [0 ; = 0x00 = @%:@nul = '\0'])
+
test_parse([uint], [012345], [12345 ; = 0x3039])
test_parse([uint], [0x234], [564 ; = 0x0234])
-test_parse([uint], [0o33], [27 ; = 0x1b = '\e'])
+test_parse([uint], [0o33], [27 ; = 0x1b = @%:@escape = '\e'])
test_parse([uint], [0b1011_1101], [189 ; = 0xbd = '\xbd'])
test_parse([uint], [12r123], [171 ; = 0xab = '\xab'])
[3], [syntax error: expected end-of-line but found `x'])
test_parserr([uint], [],
- [3], [syntax error: expected unsigned integer but found @%:@<eol>])
+ [3], [syntax error: expected unsigned integer but found @%:@eol])
test_parserr([uint], [123456],
[3], [integer 123456 out of range (must be in @<:@0 .. 65535@:>@)])
test_parse([float], [1.234], [1.234])
+test_parse([float], [@%:@nan], [@%:@nan])
+test_parse([float], [@%:@+inf], [@%:@+inf])
+test_parse([float], [@%:@inf], [@%:@+inf])
+test_parse([float], [+@%:@inf], [@%:@+inf])
+test_parse([float], [@%:@-inf], [@%:@-inf])
+test_parse([float], [-@%:@inf], [@%:@-inf])
+
AT_CLEANUP
###--------------------------------------------------------------------------
AT_CLEANUP
###--------------------------------------------------------------------------
+AT_SETUP([tvec type-char])
+
+test_parse([char], [a], ['a' ; = 97 = 0x61])
+test_parse([char], [a;?], ['a' ; = 97 = 0x61])
+test_parse([char], [a ;?], ['a' ; = 97 = 0x61])
+test_parse([char], [\\], ['\\' ; = 92 = 0x5c])
+test_parse([char], ['], ['\'' ; = 39 = 0x27])
+test_parse([char], [\'], ['\'' ; = 39 = 0x27])
+test_parse([char], ["], ['"' ; = 34 = 0x22])
+test_parse([char], [';'], [';' ; = 59 = 0x3b])
+test_parse([char], [';';?], [';' ; = 59 = 0x3b])
+test_parse([char], [';' ;?], [';' ; = 59 = 0x3b])
+test_parse([char], [\"], ['"' ; = 34 = 0x22]) # "
+test_parse([char], ['@%:@'], ['@%:@' ; = 35 = 0x23])
+
+test_parse([char], [\n], [@%:@newline ; = '\n' = 10 = 0x0a])
+test_parse([char], [\x0a], [@%:@newline ; = '\n' = 10 = 0x0a])
+
+test_parse([char], [@%:@newline], [@%:@newline ; = '\n' = 10 = 0x0a])
+test_parse([char], [@%:@lf], [@%:@newline ; = '\n' = 10 = 0x0a])
+test_parse([char], [@%:@del;?], [@%:@delete ; = '\x7f' = 127 = 0x7f])
+test_parse([char], [@%:@space ;?], [' ' ; = 32 = 0x20])
+
+test_parse([char], [' '], [' ' ; = 32 = 0x20])
+test_parse([char], ['a'], ['a' ; = 97 = 0x61])
+test_parse([char], ['\n'], [@%:@newline ; = '\n' = 10 = 0x0a])
+test_parse([char], ['\12' ;?], [@%:@newline ; = '\n' = 10 = 0x0a])
+test_parse([char], ['\{12}' ;?], [@%:@newline ; = '\n' = 10 = 0x0a])
+test_parse([char], ['\x0a'], [@%:@newline ; = '\n' = 10 = 0x0a])
+test_parse([char], ['\x{0a}'], [@%:@newline ; = '\n' = 10 = 0x0a])
+
+test_parse([char], [@%:@eof], [@%:@eof ; = -1 = -0x01])
+test_parse([char], [@%:@eof], [@%:@eof ; = -1 = -0x01])
+
+test_parserr([char], [ ],
+ [3], [syntax error: expected character but found @%:@eol])
+test_parserr([char], [''],
+ [3], [syntax error: expected character but found `''])
+test_parserr([char], ['''],
+ [3], [syntax error: expected character but found `''])
+test_parserr([char], [;],
+ [3], [syntax error: expected character but found `;'])
+test_parserr([char], [@%:@],
+ [3], [unknown character name `@%:@'])
+test_parserr([char], [';],
+ [3], [syntax error: expected `'' but found @%:@eol])
+test_parserr([char], [\],
+ [3], [syntax error: expected string escape but found @%:@eol])
+test_parserr([char], [\q],
+ [3], [syntax error: expected string escape but found `q'])
+test_parserr([char], ['\],
+ [3], [syntax error: expected string escape but found @%:@eol])
+test_parserr([char], [ab],
+ [3], [syntax error: expected end-of-line but found `b'])
+test_parserr([char], [\x{0a],
+ [3], [syntax error: expected `}' but found @%:@eol])
+test_parserr([char], [\{012],
+ [3], [syntax error: expected `}' but found @%:@eol])
+
+AT_CLEANUP
+
+###--------------------------------------------------------------------------
+AT_SETUP([tvec type-string])
+
+test_parse([string], [foo], [foo])
+test_parse([string], [foo bar], ["foo bar"])
+test_parse([string], [foo bar], ["foo bar"])
+test_parse([string], [foo "" bar], [foobar])
+test_parse([string], [ foo bar ], ["foo bar"])
+test_parse([string], [ foo @&t@
+ bar ], ["foo bar"])
+
+test_parse([string], [foo @%:@nul bar], ["foo\{0}bar"])
+
+test_parse([string], ["f" !repeat 2 { o } "bar"], [foobar])
+test_parse([string], ["{"!repeat 5{"abc"}"}"], ["{abcabcabcabcabc}"])
+
+test_parse([string], [!hex "f" 6f "o"], [foo])
+
+test_parse([string], ["foo\n"], ["foo\n"])
+
+test_parse([string], [foo\
+bar], [
+ "foo\n"
+ "bar"])
+test_parse([string], ["foo\
+bar"], [foobar])
+test_parse([string], ["foo" @%:@newline "bar" @%:@newline], [
+ "foo\n"
+ "bar\n"])
+
+test_parserr([string], [],
+ [4], [syntax error: expected string but found @%:@eof])
+test_parse([string], [""], [""])
+test_parse([string], [''], [""])
+
+test_parse([string], ["f\x{6f}o"], [foo])
+
+AT_CLEANUP
+
+###--------------------------------------------------------------------------
+AT_SETUP([tvec type-bytes])
+
+test_parse([bytes], [""], ["" ; empty])
+test_parse([bytes], [61], [61 ; a])
+test_parse([bytes], ["abc"], [616263 ; abc])
+test_parse([bytes], ["abcd"], [61626364 ; abcd])
+test_parse([bytes], ["abcde"], [61626364 65 ; abcde])
+
+test_parse([bytes], [!base64 YWJjZGVmZ2hpamtsbW5vcA==],
+ [61626364 65666768 696a6b6c 6d6e6f70 ; abcdefghijklmnop])
+test_parse([bytes],
+ [!base64 QUJDREVGR0hJSktMTU5PUGFiY2RlZmdo
+ a Wp rbG 1ub3A=],
+ [
+ 41424344 45464748 494a4b4c 4d4e4f50 ; @<:@00@:>@ ABCDEFGHIJKLMNOP
+ 61626364 65666768 696a6b6c 6d6e6f70 ; @<:@10@:>@ abcdefghijklmnop])
+
+test_parse([bytes], [6 1], [61 ; a])
+test_parserr([bytes], [6 "" 1],
+ [3], [invalid hex sequence end: Excess or nonzero padding bits])
+
+test_parse([bytes], [!base64 AA==], [00 ; .])
+test_parse([bytes], [!base64 AAA=], [0000 ; ..])
+test_parse([bytes], [!base64 AAAA], [000000 ; ...])
+test_parse([bytes], [!base64 AA], [00 ; .])
+test_parse([bytes], [!base64 AAA], [0000 ; ..])
+
+test_parserr([bytes], [0],
+ [4], [invalid hex sequence end: Excess or nonzero padding bits])
+test_parserr([bytes], [!base64 A],
+ [4], [invalid base64 sequence end: Excess or nonzero padding bits])
+test_parserr([bytes], [!base64 A=],
+ [3], [invalid base64 fragment `A=': Excess or nonzero padding bits])
+
+AT_CLEANUP
+
+###--------------------------------------------------------------------------
+AT_SETUP([tvec type-buffer])
+
+test_parse([buffer], [16], [16 B])
+test_parse([buffer], [16;?], [16 B])
+test_parse([buffer], [16 ;?], [16 B])
+test_parse([buffer], [16384], [16 kB])
+test_parse([buffer], [16777216], [16 MB])
+test_parse([buffer], [16k], [16 kB])
+test_parse([buffer], [16k;?], [16 kB])
+test_parse([buffer], [16k ;?], [16 kB])
+test_parse([buffer], [16 k], [16 kB])
+test_parse([buffer], [16 k;?], [16 kB])
+test_parse([buffer], [16 k ;?], [16 kB])
+test_parse([buffer], [16kB], [16 kB])
+test_parse([buffer], [16kB;?], [16 kB])
+test_parse([buffer], [16kB ;?], [16 kB])
+test_parse([buffer], [16 kB], [16 kB])
+test_parse([buffer], [16 kB;?], [16 kB])
+test_parse([buffer], [16 kB ;?], [16 kB])
+
+test_parserr([buffer], [16!], [3], [invalid buffer length `16!'])
+test_parserr([buffer], [16 !], [3], [invalid buffer length `16 !'])
+test_parserr([buffer], [16 k!], [3], [invalid buffer length `16 k!'])
+test_parserr([buffer], [16 kB!], [3], [invalid buffer length `16 kB!'])
+test_parserr([buffer], [16 kB !],
+ [3], [syntax error: expected end-of-line but found `!'])
+test_parserr([buffer], [16 EB], [3], [buffer length `16 EB' out of range])
+
+AT_CLEANUP
+
+###--------------------------------------------------------------------------
AT_SETUP([tvec serialize])
AT_DATA([tv],
/*----- Data structures ---------------------------------------------------*/
struct benchrun {
- struct tvec_state *tv;
- const struct tvec_env *env;
- void *ctx;
- unsigned long *n;
- const struct tvec_reg *in; struct tvec_reg *out;
- tvec_testfn *fn;
+ struct tvec_state *tv; /* test vector state */
+ const struct tvec_env *env; /* subordinate environment */
+ void *ctx; /* subordinate env's context */
+ unsigned long *n; /* iteration count address */
+ const struct tvec_reg *in; struct tvec_reg *out; /* register vectors */
+ tvec_testfn *fn; /* test function to run */
};
/*----- Global variables --------------------------------------------------*/
-struct bench_state *tvec_benchstate;
+struct bench_state *tvec_benchstate; /* common benchmarking state */
-/*----- Benchmarking ------------------------------------------------------*/
+/*----- Utilities ---------------------------------------------------------*/
+
+/* --- @normalize@ --- *
+ *
+ * Arguments: @double *x_inout@ = address of a value to normalize
+ * @const char **unit_out@ = address to store unit prefix
+ * @double scale@ = scale factor for unit steps
+ *
+ * Returns: ---
+ *
+ * Use: Adjust @*x_inout@ by a power of @scale@, and set @*unit_out@
+ * so that printing the two reflects the original value with an
+ * appropriate SI unit scaling. The @scale@ should be 1024 for
+ * binary quantities, most notably memory sizes, or 1000 for
+ * other quantities.
+ */
static void normalize(double *x_inout, const char **unit_out, double scale)
{
*x_inout = x; *unit_out = *u;
}
+/*----- Benchmark environment scaffolding ---------------------------------*/
+
+/* --- @tvec_benchsetup@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @const struct tvec_env *env@ = environment description
+ * @void *pctx@ = parent context (ignored)
+ * @void *ctx@ = context pointer to initialize
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Initialize a benchmarking environment context.
+ *
+ * The environment description must really be a @struct
+ * tvec_bench@. If the @bst@ slot is null, then a temporary
+ * benchmark state is allocated for the current test group and
+ * released at the end. Otherwise, it must be the address of a
+ * pointer to a benchmark state: if the pointer is null, then a
+ * fresh state is allocated and initialized and the pointer is
+ * updated; otherwise, the pointer is assumed to refer to an
+ * existing valid benchmark state.
+ */
+
int tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env,
void *pctx, void *ctx)
{
const struct tvec_env *subenv = b->env;
struct bench_timer *bt;
+ /* Basic initialization. */
bc->b = b; bc->bst = 0; bc->subctx = 0;
+ /* Set up the benchmarking state if it hasn't been done before. */
if (!b->bst || !*b->bst) {
bt = bench_createtimer(); if (!bt) goto fail_timer;
bc->bst = xmalloc(sizeof(*bc->bst)); bench_init(bc->bst, bt);
goto fail_timer;
else
bc->bst = *b->bst;
+
+ /* Set the default target time. */
bc->dflt_target = bc->bst->target_s;
+ /* Initialize the subordinate environment. */
if (subenv && subenv->ctxsz) bc->subctx = xmalloc(subenv->ctxsz);
if (subenv && subenv->setup && subenv->setup(tv, subenv, bc, bc->subctx))
{ xfree(bc->subctx); bc->subctx = 0; return (-1); }
+ /* All done. */
end:
return (0);
fail_timer:
tvec_skipgroup(tv, "failed to create timer"); goto end;
}
+/* --- @tvec_benchset@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @const char *var@ = variable name to set
+ * @const struct tvec_env *env@ = environment description
+ * @void *ctx@ = context pointer
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Set a special variable. The following special variables are
+ * supported.
+ *
+ * * %|@target|% is the (approximate) number of seconds to run
+ * the benchmark.
+ *
+ * Unrecognized variables are passed to the subordinate
+ * environment, if there is one.
+ */
+
int tvec_benchset(struct tvec_state *tv, const char *var,
const struct tvec_env *env, void *ctx)
{
return (0);
}
+/* --- @tvec_benchbefore@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @void *ctx@ = context pointer
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Invoke the subordinate environment's @before@ function to
+ * prepare for the benchmark.
+ */
+
int tvec_benchbefore(struct tvec_state *tv, void *ctx)
{
struct tvec_benchctx *bc = ctx;
const struct tvec_bench *b = bc->b;
const struct tvec_env *subenv = b->env;
+ /* Just call the subsidiary environment. */
if (subenv && subenv->before) return (subenv->before(tv, bc->subctx));
else return (0);
}
+/* --- @tvec_benchafter@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @void *ctx@ = context pointer
+ *
+ * Returns: ---
+ *
+ * Use: Invoke the subordinate environment's @after@ function to
+ * clean up after the benchmark.
+ */
+
void tvec_benchafter(struct tvec_state *tv, void *ctx)
{
struct tvec_benchctx *bc = ctx;
const struct tvec_bench *b = bc->b;
const struct tvec_env *subenv = b->env;
+ /* Restore the benchmark state's old target. */
bc->bst->target_s = bc->dflt_target;
+
+ /* Pass the call on to the subsidiary environment. */
if (subenv && subenv->after) subenv->after(tv, bc->subctx);
}
+/* --- @tvec_benchteardown@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @void *ctx@ = context pointer
+ *
+ * Returns: ---
+ *
+ * Use: Tear down the benchmark environment.
+ */
+
void tvec_benchteardown(struct tvec_state *tv, void *ctx)
{
struct tvec_benchctx *bc = ctx;
if (!bc) return;
b = bc->b; subenv = b->env;
+
+ /* Tear down any subsidiary environment. */
if (subenv && subenv->teardown && bc->subctx)
subenv->teardown(tv, bc->subctx);
+
+ /* If the benchmark state was temporary, then dispose of it. */
if (bc->bst) {
if (b->bst) bc->bst->target_s = bc->dflt_target;
else { bench_destroy(bc->bst); xfree(bc->bst); }
}
}
-static void benchloop_outer_direct(unsigned long n, void *p)
+/*----- Measurement machinery ---------------------------------------------*/
+
+/* --- @benchloop_...@ --- *
+ *
+ * Arguments: @unsigned long n@ = iteration count
+ * @void *ctx@ = benchmark running context
+ *
+ * Returns: ---
+ *
+ * Use: Run various kinds of benchmarking loops.
+ *
+ * * The @..._outer_...@ functions call the underlying
+ * function @n@ times in a loop; by contrast, the
+ * @..._inner_...@ functions set a register value to the
+ * chosen iteration count and expect the underlying function
+ * to perform the loop itself.
+ *
+ * * The @..._direct@ functions just call the underlying test
+ * function directly (though still through an `indirect
+ * jump' instruction); by contrast, the @..._indirect@
+ * functions invoke a subsidiary environment's @run@
+ * function, which adds additional overhead.
+ */
+
+static void benchloop_outer_direct(unsigned long n, void *ctx)
{
- struct benchrun *r = p;
- tvec_testfn *fn = r->fn; void *ctx = r->ctx;
+ struct benchrun *r = ctx;
+ tvec_testfn *fn = r->fn; void *tctx = r->ctx;
const struct tvec_reg *in = r->in; struct tvec_reg *out = r->out;
- while (n--) fn(in, out, ctx);
+ while (n--) fn(in, out, tctx);
}
-static void benchloop_inner_direct(unsigned long n, void *p)
- { struct benchrun *r = p; *r->n = n; r->fn(r->in, r->out, r->ctx); }
+static void benchloop_inner_direct(unsigned long n, void *ctx)
+ { struct benchrun *r = ctx; *r->n = n; r->fn(r->in, r->out, r->ctx); }
-static void benchloop_outer_indirect(unsigned long n, void *p)
+static void benchloop_outer_indirect(unsigned long n, void *ctx)
{
- struct benchrun *r = p;
+ struct benchrun *r = ctx;
struct tvec_state *tv = r->tv;
void (*run)(struct tvec_state *, tvec_testfn, void *) = r->env->run;
- tvec_testfn *fn = r->fn; void *ctx = r->ctx;
+ tvec_testfn *fn = r->fn; void *tctx = r->ctx;
- while (n--) run(tv, fn, ctx);
+ while (n--) run(tv, fn, tctx);
}
-static void benchloop_inner_indirect(unsigned long n, void *p)
- { struct benchrun *r = p; *r->n = n; r->env->run(r->tv, r->fn, r->ctx); }
+static void benchloop_inner_indirect(unsigned long n, void *ctx)
+ { struct benchrun *r = ctx; *r->n = n; r->env->run(r->tv, r->fn, r->ctx); }
+
+/* --- @tvec_benchrun@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @tvec_testfn *fn@ = test function to run
+ * @void *ctx@ = context pointer for the test function
+ *
+ * Returns: ---
+ *
+ * Use: Measures and reports the performance of a test function.
+ *
+ *
+ */
void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
{
unsigned f = 0;
#define f_any 1u
+ /* Fill in the easy parts of the run context. */
r.tv = tv; r.env = subenv; r.ctx = bc->subctx;
r.in = tv->in; r.out = tv->out; r.fn = fn;
+ /* Decide on the run function to select. */
if (b->riter >= 0) {
r.n = &TVEC_REG(tv, in, b->riter)->v.u;
loopfn = subenv && subenv->run ?
benchloop_outer_indirect : benchloop_outer_direct;
}
+ /* Decide on the kind of unit and the base count. */
base = b->niter;
if (b->rbuf < 0) unit = TVBU_OP;
else { unit = TVBU_BYTE; base *= TVEC_REG(tv, in, b->rbuf)->v.bytes.sz; }
+ /* Construct a description of the test using the identifier registers. */
for (rd = tv->test->regs; rd->name; rd++)
if (rd->f&TVRF_ID) {
if (f&f_any) dstr_puts(&d, ", ");
TVSF_COMPACT, &dstr_printops, &d);
}
+ /* Run the benchmark. */
o->ops->bbench(o, d.buf, unit);
if (bench_measure(&tm, bc->bst, base, loopfn, &r))
o->ops->ebench(o, d.buf, unit, 0);
#undef f_any
}
+/*----- Output utilities --------------------------------------------------*/
+
+/* --- @tvec_benchreport@ --- *
+ *
+ * Arguments: @const struct gprintf_ops *gops@ = print operations
+ * @void *go@ = print destination
+ * @unsigned unit@ = the unit being measured (~TVBU_...@)
+ * @const struct bench_timing *tm@ = the benchmark result
+ *
+ * Returns: ---
+ *
+ * Use: Formats a report about the benchmark performance. This
+ * function is intended to be called on by an output
+ * @ebench@ function.
+ */
+
void tvec_benchreport(const struct gprintf_ops *gops, void *go,
unsigned unit, const struct bench_timing *tm)
{
/*----- Output ------------------------------------------------------------*/
+/* --- @tvec_error@, @tvec_error_v@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *msg@, @va_list ap@ = error message
+ *
+ * Returns: @-1@.
+ *
+ * Use: Report an error. Errors are distinct from test failures,
+ * and indicate that a problem was encountered which compromised
+ * the activity of testing.
+ */
+
int tvec_error(struct tvec_state *tv, const char *msg, ...)
{
va_list ap;
va_start(ap, msg); tvec_error_v(tv, msg, &ap); va_end(ap);
- tv->f |= TVSF_ERROR; return (-1);
+ return (-1);
}
int tvec_error_v(struct tvec_state *tv, const char *msg, va_list *ap)
- { tv->output->ops->error(tv->output, msg, ap); return (-1); }
+{
+ tv->output->ops->error(tv->output, msg, ap);
+ tv->f |= TVSF_ERROR; return (-1);
+}
+
+/* --- @tvec_notice@, @tvec_notice_v@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *msg@, @va_list ap@ = message
+ *
+ * Returns: ---
+ *
+ * Use: Output a notice: essentially, some important information
+ * which doesn't fit into any of the existing categories.
+ */
void tvec_notice(struct tvec_state *tv, const char *msg, ...)
{
void tvec_notice_v(struct tvec_state *tv, const char *msg, va_list *ap)
{ tv->output->ops->notice(tv->output, msg, ap); }
-int tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...)
-{
- va_list ap;
-
- va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap);
- return (-1);
-}
-int tvec_syntax_v(struct tvec_state *tv, int ch,
- const char *expect, va_list *ap)
-{
- dstr d = DSTR_INIT;
- char found[8];
-
- switch (ch) {
- case EOF: strcpy(found, "#<eof>"); break;
- case '\n': strcpy(found, "#<eol>"); ungetc(ch, tv->fp); break;
- default:
- if (isprint(ch)) sprintf(found, "`%c'", ch);
- else sprintf(found, "#<\\x%02x>", ch);
- break;
- }
- dstr_vputf(&d, expect, ap);
- tvec_error(tv, "syntax error: expected %s but found %s", expect, found);
- return (-1);
-}
+/*----- Test processing ---------------------------------------------------*/
void tvec_skipgroup(struct tvec_state *tv, const char *excuse, ...)
{
va_list ap;
+
va_start(ap, excuse); tvec_skipgroup_v(tv, excuse, &ap); va_end(ap);
}
void tvec_skipgroup_v(struct tvec_state *tv, const char *excuse, va_list *ap)
}
}
-/*----- Main machinery ----------------------------------------------------*/
+/*----- Parsing -----------------------------------------------------------*/
-struct groupstate {
- void *ctx;
-};
-#define GROUPSTATE_INIT { 0 }
+int tvec_syntax(struct tvec_state *tv, int ch, const char *expect, ...)
+{
+ va_list ap;
+
+ va_start(ap, expect); tvec_syntax_v(tv, ch, expect, &ap); va_end(ap);
+ return (-1);
+}
+int tvec_syntax_v(struct tvec_state *tv, int ch,
+ const char *expect, va_list *ap)
+{
+ dstr d = DSTR_INIT;
+ char found[8];
+
+ switch (ch) {
+ case EOF: strcpy(found, "#eof"); break;
+ case '\n': strcpy(found, "#eol"); ungetc(ch, tv->fp); break;
+ default:
+ if (isprint(ch)) sprintf(found, "`%c'", ch);
+ else sprintf(found, "#\\x%02x", ch);
+ break;
+ }
+ dstr_vputf(&d, expect, ap);
+ tvec_error(tv, "syntax error: expected %s but found %s", d.buf, found);
+ dstr_destroy(&d); return (-1);
+}
void tvec_skipspc(struct tvec_state *tv)
{
return (0);
}
+/*----- Main machinery ----------------------------------------------------*/
+
+struct groupstate {
+ void *ctx;
+};
+#define GROUPSTATE_INIT { 0 }
+
void tvec_resetoutputs(struct tvec_state *tv)
{
const struct tvec_regdef *rd;
/*----- Main code ---------------------------------------------------------*/
+/* Table of output formats. */
static const struct outform {
const char *name;
struct tvec_output *(*makefn)(FILE *fp);
{ 0, 0 }
};
+/* Configuration for ad-hoc testing. */
const struct tvec_config tvec_adhocconfig =
{ 0, 1, 1, sizeof(struct tvec_reg) };
+/* --- @find_outform@ ---
+ *
+ * Arguments: @const char *p@ = output name
+ *
+ * Returns: Pointer to output format record.
+ *
+ * Use: Looks up an output format by name. Reports a fatal error if
+ * no matching record is found.
+ */
+
static const struct outform *find_outform(const char *p)
{
const struct outform *best = 0, *of;
else die(2, "unknown output format `%s'", optarg);
}
+/* --- @version@, @usage@, @help@ --- *
+ *
+ * Arguments: @FILE *fp@ = stream to write on
+ *
+ * Returns: ---
+ *
+ * Use: Output information about the program.
+ */
+
static void version(FILE *fp)
{ pquis(fp, "$, mLib test-vector framework version " VERSION "\n"); }
", fp);
}
+/* --- @tvec_parseargs@ --- *
+ *
+ * Arguments: @int argc@ = number of command-line arguments
+ * @char *argv[]@ = vector of argument strings
+ * @struct tvec_state *tv_out@ = test vector state to initialize
+ * @int *argpos_out@ = where to leave unread argument index
+ * @const struct tvec_config *cofig@ = test vector configuration
+ *
+ * Returns: ---
+ *
+ * Use: Parse arguments and set up the test vector state @*tv_out@.
+ * If errors occur, print messages to standard error and exit
+ * with status 2.
+ */
+
void tvec_parseargs(int argc, char *argv[], struct tvec_state *tv_out,
int *argpos_out, const struct tvec_config *config)
{
tvec_begin(tv_out, config, o); *argpos_out = optind;
}
+/* --- @tvec_readstdin@, @tvec_readfile@, @tvec_readarg@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @const char *file@ = pathname of file to read
+ * @const char *arg@ = argument to interpret
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Read test vector data from stdin or a named file. The
+ * @tvec_readarg@ function reads from stdin if @arg@ is `%|-|%',
+ * and from the named file otherwise.
+ */
+
int tvec_readstdin(struct tvec_state *tv)
{ return (tvec_read(tv, "<stdin>", stdin)); }
return (rc);
}
-int tvec_readdflt(struct tvec_state *tv, const char *file)
+int tvec_readarg(struct tvec_state *tv, const char *arg)
+{
+ int rc;
+
+ if (STRCMP(arg, ==, "-")) rc = tvec_readstdin(tv);
+ else rc = tvec_readfile(tv, arg);
+ return (rc);
+}
+
+/* --- @tvec_readdflt@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @const char *dflt@ = defsault filename or null
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Reads from the default test vector data. If @file@ is null,
+ * then read from standard input, unless that's a terminal; if
+ * @file@ is not null, then read the named file, looking in the
+ * directory named by the `%|srcdir|%' environment variable if
+ * that's set, or otherwise in the current directory.
+ */
+
+int tvec_readdflt(struct tvec_state *tv, const char *dflt)
{
dstr d = DSTR_INIT;
const char *p;
int rc;
- if (file) {
+ if (dflt) {
p = getenv("srcdir");
- if (p) { dstr_putf(&d, "%s/%s", p, file); file = d.buf; }
- rc = tvec_readfile(tv, file);
+ if (p) { dstr_putf(&d, "%s/%s", p, dflt); dflt = d.buf; }
+ rc = tvec_readfile(tv, dflt);
} else if (isatty(0))
rc = tvec_error(tv, "use `-' to force reading from interactive stdin");
else
return (rc);
}
-int tvec_readarg(struct tvec_state *tv, const char *arg)
-{
- int rc;
-
- if (STRCMP(arg, ==, "-")) rc = tvec_readstdin(tv);
- else rc = tvec_readfile(tv, arg);
- return (rc);
-}
+/* --- @tvec_readargs@ --- *
+ *
+ * Arguments: @int argc@ = number of command-line arguments
+ * @char *argv[]@ = vector of argument strings
+ * @struct tvec_state *tv@ = test vector state
+ * @int *argpos_inout@ = current argument position (updated)
+ * @const char *dflt@ = default filename or null
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Reads from the sources indicated by the command-line
+ * arguments, in order, interpreting each as for @tvec_readarg@;
+ * if no arguments are given then read from @dflt@ as for
+ * @tvec_readdflt@.
+ */
int tvec_readargs(int argc, char *argv[], struct tvec_state *tv,
int *argpos_inout, const char *dflt)
int i = *argpos_inout;
int rc;
- if (i == argc) rc = tvec_readdflt(tv, dflt);
+ if (i == argc)
+ rc = tvec_readdflt(tv, dflt);
else {
rc = 0;
while (i < argc)
return (rc);
}
+/* --- @tvec_main@ --- *
+ *
+ * Arguments: @int argc@ = number of command-line arguments
+ * @char *argv[]@ = vector of argument strings
+ * @const struct tvec_config *cofig@ = test vector configuration
+ * @const char *dflt@ = default filename or null
+ *
+ * Returns: Exit code.
+ *
+ * Use: All-in-one test vector front-end. Parse options from the
+ * command-line as for @tvec_parseargs@, and then process the
+ * remaining positional arguments as for @tvec_readargs@. The
+ * function constructs and disposes of a test vector state.
+ */
+
int tvec_main(int argc, char *argv[],
const struct tvec_config *config, const char *dflt)
{
#include "config.h"
#include <assert.h>
+#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "alloc.h"
#include "bench.h"
#include "dstr.h"
+#include "macros.h"
#include "quis.h"
#include "report.h"
#include "tvec.h"
/*----- Common machinery --------------------------------------------------*/
+/* --- @regdisp@ --- *
+ *
+ * Arguments: @unsigned disp@ = a @TVRD_...@ disposition code
+ *
+ * Returns: A human-readable adjective describing the register
+ * disposition.
+ */
+
static const char *regdisp(unsigned disp)
{
switch (disp) {
}
}
+/* --- @getenv_boolean@ --- *
+ *
+ * Arguments: @const char *var@ = environment variable name
+ * @int dflt@ = default value
+ *
+ * Returns: @0@ if the variable is set to something falseish, @1@ if it's
+ * set to something truish, or @dflt@ otherwise.
+ */
+
static int getenv_boolean(const char *var, int dflt)
{
const char *p;
STRCMP(p, ==, "0"))
return (0);
else {
- moan("unexpected value `%s' for boolean environment variable `%s'",
+ moan("ignoring unexpected value `%s' for environment variable `%s'",
var, p);
return (dflt);
}
}
+/* --- @register_maxnamelen@ --- *
+ *
+ * Arguments: @const struct tvec_state *tv@ = test vector state
+ *
+ * Returns: The maximum length of a register name in the current test.
+ */
+
static int register_maxnamelen(const struct tvec_state *tv)
{
const struct tvec_regdef *rd;
return (maxlen);
}
+/*----- Output formatting -------------------------------------------------*/
+
+/* We have two main jobs in output formatting: trimming trailing blanks; and
+ * adding a prefix to each line.
+ *
+ * This is somehow much more complicated than it ought to be.
+ */
+
+struct format {
+ FILE *fp; /* output file */
+ const char *prefix, *pfxtail, *pfxlim; /* prefix pointers */
+ dstr w; /* trailing whitespace */
+ unsigned f; /* flags */
+#define FMTF_NEWL 1u /* start of output line */
+};
+
+/* Support macros. These assume `fmt' is defined as a pointer to the `struct
+ * format' state.
+ */
+
+#define SPLIT_RANGE(tail, base, limit) do { \
+ /* Set TAIL to point just after the last nonspace character between \
+ * BASE and LIMIT. If there are no nonspace characters, then set \
+ * TAIL to equal BASE. \
+ */ \
+ \
+ for (tail = limit; tail > base && ISSPACE(tail[-1]); tail--); \
+} while (0)
+
+#define PUT_RANGE(base, limit) do { \
+ /* Write the range of characters between BASE and LIMIT to the output \
+ * file. Return immediately on error. \
+ */ \
+ \
+ size_t n = limit - base; \
+ if (fwrite(base, 1, n, fmt->fp) < n) return (-1); \
+} while (0)
+
+#define PUT_CHAR(ch) do { \
+ /* Write CH to the output. Return immediately on error. */ \
+ \
+ if (putc(ch, fmt->fp) == EOF) return (-1); \
+} while (0)
+
+#define PUT_PREFIX do { \
+ /* Output the prefix, if there is one. Return immediately on error. */ \
+ \
+ if (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxlim); \
+} while (0)
+
+#define PUT_SAVED do { \
+ /* Output the saved trailing blank material in the buffer. */ \
+ \
+ size_t n = fmt->w.len; \
+ if (n && fwrite(fmt->w.buf, 1, n, fmt->fp) < n) return (-1); \
+} while (0)
+
+#define PUT_PFXINB do { \
+ /* Output the initial nonblank portion of the prefix, if there is \
+ * one. Return immediately on error. \
+ */ \
+ \
+ if (fmt->prefix) PUT_RANGE(fmt->prefix, fmt->pfxtail); \
+} while (0)
+
+#define SAVE_PFXTAIL do { \
+ /* Save the trailing blank portion of the prefix. */ \
+ \
+ if (fmt->prefix) \
+ DPUTM(&fmt->w, fmt->pfxtail, fmt->pfxlim - fmt->pfxtail); \
+} while (0)
+
+/* --- @init_fmt@ --- *
+ *
+ * Arguments: @struct format *fmt@ = formatting state to initialize
+ * @FILE *fp@ = output file
+ * @const char *prefix@ = prefix string (or null if empty)
+ *
+ * Returns: ---
+ *
+ * Use: Initialize a formatting state.
+ */
+
+static void init_fmt(struct format *fmt, FILE *fp, const char *prefix)
+{
+ const char *q, *l;
+
+ /* Basics. */
+ fmt->fp = fp;
+ fmt->f = FMTF_NEWL;
+ dstr_create(&fmt->w);
+
+ /* Prefix portions. */
+ if (!prefix || !*prefix)
+ fmt->prefix = fmt->pfxtail = fmt->pfxlim = 0;
+ else {
+ fmt->prefix = prefix;
+ l = fmt->pfxlim = prefix + strlen(prefix);
+ SPLIT_RANGE(q, prefix, l); fmt->pfxtail = q;
+ DPUTM(&fmt->w, q, l - q);
+ }
+}
+
+/* --- @destroy_fmt@ --- *
+ *
+ * Arguments: @struct format *fmt@ = formatting state
+ * @unsigned f@ = flags (@DFF_...@)
+ *
+ * Returns: ---
+ *
+ * Use: Releases a formatting state and the resources it holds.
+ * Close the file if @DFF_CLOSE@ is set in @f@; otherwise leave
+ * it open (in case it's @stderr@ or something).
+ */
+
+#define DFF_CLOSE 1u
+static void destroy_fmt(struct format *fmt, unsigned f)
+{
+ if (f&DFF_CLOSE) fclose(fmt->fp);
+ dstr_destroy(&fmt->w);
+}
+
+/* --- @format_char@ --- *
+ *
+ * Arguments: @struct format *fmt@ = formatting state
+ * @int ch@ = character to write
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Write a single character to the output.
+ */
+
+static int format_char(struct format *fmt, int ch)
+{
+ if (ch == '\n') {
+ if (fmt->f&FMTF_NEWL) PUT_PFXINB;
+ PUT_CHAR('\n'); fmt->f |= FMTF_NEWL; DRESET(&fmt->w);
+ } else if (isspace(ch))
+ DPUTC(&fmt->w, ch);
+ else {
+ if (fmt->f&FMTF_NEWL) { PUT_PFXINB; fmt->f &= ~FMTF_NEWL; }
+ PUT_SAVED; PUT_CHAR(ch); DRESET(&fmt->w);
+ }
+ return (0);
+}
+
+/* --- @format_string@ --- *
+ *
+ * Arguments: @struct format *fmt@ = formatting state
+ * @const char *p@ = string to write
+ * @size_t sz@ = length of string
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Write a string to the output.
+ */
+
+static int format_string(struct format *fmt, const char *p, size_t sz)
+{
+ const char *q, *r, *l = p + sz;
+
+ /* This is rather vexing. There are a small number of jobs to do, but the
+ * logic for deciding which to do when gets rather hairy if, as I've tried
+ * here, one aims to minimize the number of decisions being checked, so
+ * it's worth canning them into macros.
+ *
+ * Here, a `blank' is a whitespace character other than newline. The input
+ * buffer consists of one or more `segments', each of which consists of:
+ *
+ * * an initial portion, which is either empty or ends with a nonblank
+ * character;
+ *
+ * * a suffix which consists only of blanks; and
+ *
+ * * an optional newline.
+ *
+ * All segments except the last end with a newline.
+ */
+
+#define SPLIT_SEGMENT do { \
+ /* Determine the bounds of the current segment. If there is a final \
+ * newline, then q is non-null and points to this newline; otherwise, \
+ * q is null. The initial portion of the segment lies between p .. r \
+ * and the blank suffix lies between r .. q (or r .. l if q is null). \
+ * This sounds awkward, but the suffix is only relevant if there is \
+ * no newline. \
+ */ \
+ \
+ q = memchr(p, '\n', l - p); SPLIT_RANGE(r, p, q ? q : l); \
+} while (0)
+
+#define PUT_NONBLANK do { \
+ /* Output the initial portion of the segment. */ \
+ \
+ PUT_RANGE(p, r); \
+} while (0)
+
+#define PUT_NEWLINE do { \
+ /* Write a newline, and advance to the next segment. */ \
+ \
+ PUT_CHAR('\n'); p = q + 1; \
+} while (0)
+
+#define SAVE_TAIL do { \
+ /* Save the trailing blank portion of the segment in the buffer. \
+ * Assumes that there is no newline, since otherwise the suffix would \
+ * be omitted. \
+ */ \
+ \
+ DPUTM(&fmt->w, r, l - r); \
+} while (0)
+
+ /* Determine the bounds of the first segment. Handling this is the most
+ * complicated part of this function.
+ */
+ SPLIT_SEGMENT;
+
+ if (!q) {
+ /* This is the only segment. We'll handle the whole thing here.
+ *
+ * If there's an initial nonblank portion, then we need to write that
+ * out. Furthermore, if we're at the start of the line then we'll need
+ * to write the prefix, and if there's saved blank material then we'll
+ * need to write that. Otherwise, there's only blank stuff, which we
+ * accumulate in the buffer.
+ *
+ * If we're at the start of a line here, then
+ */
+
+ if (r > p) {
+ if (fmt->f&FMTF_NEWL) { PUT_PFXINB; fmt->f &= ~FMTF_NEWL; }
+ PUT_SAVED; PUT_NONBLANK; DRESET(&fmt->w);
+ }
+ SAVE_TAIL;
+ return (0);
+ }
+
+ /* There is at least one more segment, so we know that there'll be a line
+ * to output.
+ */
+ if (fmt->f&FMTF_NEWL) PUT_PFXINB;
+ if (r > p) { PUT_SAVED; PUT_NONBLANK; }
+ PUT_NEWLINE; DRESET(&fmt->w);
+ SPLIT_SEGMENT;
+
+ /* Main loop over whole segments with trailing newlines. For each one, we
+ * know that we're starting at the beginning of a line and there's a final
+ * newline, so we write the initial prefix and drop the trailing blanks.
+ */
+ while (q) {
+ PUT_PREFIX; PUT_NONBLANK; PUT_NEWLINE;
+ SPLIT_SEGMENT;
+ }
+
+ /* At the end, there's no final newline. If there's nonblank material,
+ * then we can write the prefix and the nonblank stuff. Otherwise, stash
+ * the blank stuff (including the trailing blanks of the prefix) and leave
+ * the newline flag set.
+ */
+ if (r > p) { PUT_PREFIX; PUT_NONBLANK; fmt->f &= ~FMTF_NEWL; }
+ else { fmt->f |= FMTF_NEWL; SAVE_PFXTAIL; }
+ SAVE_TAIL;
+
+#undef SPLIT_SEGMENT
+#undef PUT_NONBLANK
+#undef PUT_NEWLINE
+#undef SAVE_TAIL
+
+ return (0);
+}
+
+#undef SPLIT_RANGE
+#undef PUT_RANGE
+#undef PUT_PREFIX
+#undef PUT_PFXINB
+#undef PUT_SAVED
+#undef PUT_CHAR
+#undef SAVE_PFXTAIL
+
/*----- Skeleton ----------------------------------------------------------*/
/*
static void ..._bsession(struct tvec_output *o, struct tvec_state *tv)
struct human_output {
struct tvec_output _o;
struct tvec_state *tv;
- FILE *fp;
+ struct format fmt;
+ char *outbuf; size_t outsz;
dstr scoreboard;
unsigned attr;
int maxlen;
int sep = 0;
if (!diff || !(h->f&HOF_COLOUR)) return;
- fputs("\x1b[", h->fp);
+ fputs("\x1b[", h->fmt.fp);
if (diff&HAF_BOLD) {
- if (attr&HAF_BOLD) putc('1', h->fp);
- else { putc('0', h->fp); diff = h->attr; }
+ if (attr&HAF_BOLD) putc('1', h->fmt.fp);
+ else { putc('0', h->fmt.fp); diff = h->attr; }
sep = ';';
}
if (diff&(HAF_FG | HAF_FGMASK)) {
if (attr&HAF_FG)
- set_colour(h->fp, &sep, "3", "9", (attr&HAF_FGMASK) >> HAF_FGSHIFT);
+ set_colour(h->fmt.fp, &sep, "3", "9",
+ (attr&HAF_FGMASK) >> HAF_FGSHIFT);
else
- { if (sep) putc(sep, h->fp); fputs("39", h->fp); sep = ';'; }
+ { if (sep) putc(sep, h->fmt.fp); fputs("39", h->fmt.fp); sep = ';'; }
}
if (diff&(HAF_BG | HAF_BGMASK)) {
if (attr&HAF_BG)
- set_colour(h->fp, &sep, "4", "10", (attr&HAF_BGMASK) >> HAF_BGSHIFT);
+ set_colour(h->fmt.fp, &sep, "4", "10",
+ (attr&HAF_BGMASK) >> HAF_BGSHIFT);
else
- { if (sep) putc(sep, h->fp); fputs("49", h->fp); sep = ';'; }
+ { if (sep) putc(sep, h->fmt.fp); fputs("49", h->fmt.fp); sep = ';'; }
}
- putc('m', h->fp); h->attr = attr;
+ putc('m', h->fmt.fp); h->attr = attr;
#undef f_any
}
if (h->f&HOF_PROGRESS) {
n = strlen(h->tv->test->name) + 2 + h->scoreboard.len;
- for (i = 0; i < n; i++) fputs("\b \b", h->fp);
+ for (i = 0; i < n; i++) fputs("\b \b", h->fmt.fp);
h->f &= ~HOF_PROGRESS;
}
}
case '_': setattr(h, HA_SKIP); break;
default: setattr(h, 0); break;
}
- putc(ch, h->fp); setattr(h, 0);
+ putc(ch, h->fmt.fp); setattr(h, 0);
}
static void show_progress(struct human_output *h)
const char *p, *l;
if (tv->test && (h->f&HOF_TTY) && !(h->f&HOF_PROGRESS)) {
- fprintf(h->fp, "%s: ", tv->test->name);
+ fprintf(h->fmt.fp, "%s: ", tv->test->name);
if (!(h->f&HOF_COLOUR))
- dstr_write(&h->scoreboard, h->fp);
+ dstr_write(&h->scoreboard, h->fmt.fp);
else for (p = h->scoreboard.buf, l = p + h->scoreboard.len; p < l; p++)
write_scoreboard_char(h, *p);
- fflush(h->fp); h->f |= HOF_PROGRESS;
+ fflush(h->fmt.fp); h->f |= HOF_PROGRESS;
}
}
#define FLUSH(fp) do if (f&f_flush) fflush(fp); while (0)
- if (fp != h->fp) f |= f_flush;
+ if (fp != h->fmt.fp) f |= f_flush;
if (file) {
- setattr(h, HFG(CYAN)); FLUSH(h->fp); fputs(file, fp); FLUSH(fp);
- setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp);
- setattr(h, HFG(CYAN)); FLUSH(h->fp); fprintf(fp, "%u", lno); FLUSH(fp);
- setattr(h, HFG(BLUE)); FLUSH(h->fp); fputc(':', fp); FLUSH(fp);
- setattr(h, 0); FLUSH(h->fp); fputc(' ', fp);
+ setattr(h, HFG(CYAN)); FLUSH(h->fmt.fp);
+ fputs(file, fp); FLUSH(fp);
+ setattr(h, HFG(BLUE)); FLUSH(h->fmt.fp);
+ fputc(':', fp); FLUSH(fp);
+ setattr(h, HFG(CYAN)); FLUSH(h->fmt.fp);
+ fprintf(fp, "%u", lno); FLUSH(fp);
+ setattr(h, HFG(BLUE)); FLUSH(h->fmt.fp);
+ fputc(':', fp); FLUSH(fp);
+ setattr(h, 0); FLUSH(h->fmt.fp);
+ fputc(' ', fp);
}
#undef f_flush
#undef FLUSH
}
+static int human_writech(void *go, int ch)
+ { struct human_output *h = go; return (format_char(&h->fmt, ch)); }
+
+static int human_writem(void *go, const char *p, size_t sz)
+ { struct human_output *h = go; return (format_string(&h->fmt, p, sz)); }
+
+static int human_nwritef(void *go, size_t maxsz, const char *p, ...)
+{
+ struct human_output *h = go;
+ size_t n;
+ va_list ap;
+
+ va_start(ap, p);
+ n = gprintf_memputf(&h->outbuf, &h->outsz, maxsz, p, ap);
+ va_end(ap);
+ return (format_string(&h->fmt, h->outbuf, n));
+}
+
+static const struct gprintf_ops human_printops =
+ { human_writech, human_writem, human_nwritef };
+
static void human_bsession(struct tvec_output *o, struct tvec_state *tv)
{ struct human_output *h = (struct human_output *)o; h->tv = tv; }
static void report_skipped(struct human_output *h, unsigned n)
{
if (n) {
- fprintf(h->fp, " (%u ", n);
- setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
- fputc(')', h->fp);
+ fprintf(h->fmt.fp, " (%u ", n);
+ setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
+ fputc(')', h->fmt.fp);
}
}
all_run = all_win + all_lose, grps_run = grps_win + grps_lose;
if (!all_lose) {
- setattr(h, HA_WIN); fputs("PASSED", h->fp); setattr(h, 0);
- fprintf(h->fp, " %s%u %s",
+ setattr(h, HA_WIN); fputs("PASSED", h->fmt.fp); setattr(h, 0);
+ fprintf(h->fmt.fp, " %s%u %s",
!(all_skip || grps_skip) ? "all " : "",
all_win, all_win == 1 ? "test" : "tests");
report_skipped(h, all_skip);
- fprintf(h->fp, " in %u %s",
+ fprintf(h->fmt.fp, " in %u %s",
grps_win, grps_win == 1 ? "group" : "groups");
report_skipped(h, grps_skip);
} else {
- setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
- fprintf(h->fp, " %u out of %u %s",
+ setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
+ fprintf(h->fmt.fp, " %u out of %u %s",
all_lose, all_run, all_run == 1 ? "test" : "tests");
report_skipped(h, all_skip);
- fprintf(h->fp, " in %u out of %u %s",
+ fprintf(h->fmt.fp, " in %u out of %u %s",
grps_lose, grps_run, grps_run == 1 ? "group" : "groups");
report_skipped(h, grps_skip);
}
- fputc('\n', h->fp);
+ fputc('\n', h->fmt.fp);
if (tv->f&TVSF_ERROR) {
- setattr(h, HA_ERR); fputs("ERRORS", h->fp); setattr(h, 0);
- fputs(" found in input; tests may not have run correctly\n", h->fp);
+ setattr(h, HA_ERR); fputs("ERRORS", h->fmt.fp); setattr(h, 0);
+ fputs(" found in input; tests may not have run correctly\n", h->fmt.fp);
}
h->tv = 0; return (tv->f&TVSF_ERROR ? 2 : tv->all[TVOUT_LOSE] ? 1 : 0);
if (!(~h->f&(HOF_TTY | HOF_PROGRESS))) {
h->f &= ~HOF_PROGRESS;
- setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
+ setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
} else {
- fprintf(h->fp, "%s: ", h->tv->test->name);
- setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
+ fprintf(h->fmt.fp, "%s: ", h->tv->test->name);
+ setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
}
- if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); }
- fputc('\n', h->fp);
+ if (excuse) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, excuse, *ap); }
+ fputc('\n', h->fmt.fp);
}
static void human_egroup(struct tvec_output *o)
skip = tv->curr[TVOUT_SKIP], run = win + lose;
if (h->f&HOF_TTY) h->f &= ~HOF_PROGRESS;
- else fprintf(h->fp, "%s:", h->tv->test->name);
+ else fprintf(h->fmt.fp, "%s:", h->tv->test->name);
if (lose) {
- fprintf(h->fp, " %u/%u ", lose, run);
- setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
+ fprintf(h->fmt.fp, " %u/%u ", lose, run);
+ setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
report_skipped(h, skip);
} else {
- fputc(' ', h->fp); setattr(h, HA_WIN); fputs("ok", h->fp); setattr(h, 0);
+ fputc(' ', h->fmt.fp); setattr(h, HA_WIN);
+ fputs("ok", h->fmt.fp); setattr(h, 0);
report_skipped(h, skip);
}
- fputc('\n', h->fp);
+ fputc('\n', h->fmt.fp);
}
static void human_btest(struct tvec_output *o)
struct tvec_state *tv = h->tv;
clear_progress(h);
- report_location(h, h->fp, tv->infile, tv->test_lno);
- fprintf(h->fp, "`%s' ", tv->test->name);
- setattr(h, HA_SKIP); fputs("skipped", h->fp); setattr(h, 0);
- if (excuse) { fputs(": ", h->fp); vfprintf(h->fp, excuse, *ap); }
- fputc('\n', h->fp);
+ report_location(h, h->fmt.fp, tv->infile, tv->test_lno);
+ fprintf(h->fmt.fp, "`%s' ", tv->test->name);
+ setattr(h, HA_SKIP); fputs("skipped", h->fmt.fp); setattr(h, 0);
+ if (excuse) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, excuse, *ap); }
+ fputc('\n', h->fmt.fp);
}
static void human_fail(struct tvec_output *o,
struct tvec_state *tv = h->tv;
clear_progress(h);
- report_location(h, h->fp, tv->infile, tv->test_lno);
- fprintf(h->fp, "`%s' ", tv->test->name);
- setattr(h, HA_LOSE); fputs("FAILED", h->fp); setattr(h, 0);
- if (detail) { fputs(": ", h->fp); vfprintf(h->fp, detail, *ap); }
- fputc('\n', h->fp);
+ report_location(h, h->fmt.fp, tv->infile, tv->test_lno);
+ fprintf(h->fmt.fp, "`%s' ", tv->test->name);
+ setattr(h, HA_LOSE); fputs("FAILED", h->fmt.fp); setattr(h, 0);
+ if (detail) { fputs(": ", h->fmt.fp); vfprintf(h->fmt.fp, detail, *ap); }
+ fputc('\n', h->fmt.fp);
}
static void human_dumpreg(struct tvec_output *o,
struct human_output *h = (struct human_output *)o;
const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
- fprintf(h->fp, "%*s%s %s = ", 10 + h->maxlen - n, "", ds, rd->name);
+ clear_progress(h);
+ gprintf(&human_printops, h, "%*s%s %s = ",
+ 10 + h->maxlen - n, "", ds, rd->name);
if (h->f&HOF_COLOUR) {
if (!rv) setattr(h, HFG(YELLOW));
else if (disp == TVRD_FOUND) setattr(h, HFG(RED));
else if (disp == TVRD_EXPECT) setattr(h, HFG(GREEN));
}
- if (!rv) fprintf(h->fp, "#<unset>");
- else rd->ty->dump(rv, rd, 0, &file_printops, h->fp);
- setattr(h, 0); fputc('\n', h->fp);
+ if (!rv) gprintf(&human_printops, h, "#unset");
+ else rd->ty->dump(rv, rd, 0, &human_printops, h);
+ setattr(h, 0); format_char(&h->fmt, '\n');
}
static void human_etest(struct tvec_output *o, unsigned outcome)
default: abort();
}
dstr_putc(&h->scoreboard, ch);
- write_scoreboard_char(h, ch); fflush(h->fp);
+ write_scoreboard_char(h, ch); fflush(h->fmt.fp);
}
}
struct tvec_state *tv = h->tv;
clear_progress(h);
- fprintf(h->fp, "%s: %s: ", tv->test->name, ident); fflush(h->fp);
+ fprintf(h->fmt.fp, "%s: %s: ", tv->test->name, ident); fflush(h->fmt.fp);
}
static void human_ebench(struct tvec_output *o,
const struct bench_timing *tm)
{
struct human_output *h = (struct human_output *)o;
- tvec_benchreport(&file_printops, h->fp, unit, tm); fputc('\n', h->fp);
+
+ tvec_benchreport(&human_printops, h->fmt.fp, unit, tm);
+ fputc('\n', h->fmt.fp);
}
static void human_report(struct tvec_output *o, const char *msg, va_list *ap)
dstr_vputf(&d, msg, ap); dstr_putc(&d, '\n');
- clear_progress(h); fflush(h->fp);
+ clear_progress(h); fflush(h->fmt.fp);
fprintf(stderr, "%s: ", QUIS);
report_location(h, stderr, tv->infile, tv->lno);
fwrite(d.buf, 1, d.len, stderr);
if (h->f&HOF_DUPERR) {
- report_location(h, h->fp, tv->infile, tv->lno);
- fwrite(d.buf, 1, d.len, h->fp);
+ report_location(h, h->fmt.fp, tv->infile, tv->lno);
+ fwrite(d.buf, 1, d.len, h->fmt.fp);
}
show_progress(h);
}
{
struct human_output *h = (struct human_output *)o;
- if (h->f&HOF_DUPERR) fclose(h->fp);
+ destroy_fmt(&h->fmt, h->f&HOF_DUPERR ? DFF_CLOSE : 0);
dstr_destroy(&h->scoreboard);
- xfree(h);
+ xfree(h->outbuf); xfree(h);
}
static const struct tvec_outops human_ops = {
h = xmalloc(sizeof(*h)); h->_o.ops = &human_ops;
h->f = 0; h->attr = 0;
- h->fp = fp;
+ init_fmt(&h->fmt, fp, 0);
+ h->outbuf = 0; h->outsz = 0;
switch (getenv_boolean("TVEC_TTY", -1)) {
case 1: h->f |= HOF_TTY; break;
struct tap_output {
struct tvec_output _o;
struct tvec_state *tv;
- FILE *fp;
- dstr d;
+ struct format fmt;
+ char *outbuf; size_t outsz;
int maxlen;
- unsigned f;
-#define TOF_FRESHLINE 1u
};
static int tap_writech(void *go, int ch)
-{
- struct tap_output *t = go;
-
- if (t->f&TOF_FRESHLINE) {
- if (fputs("## ", t->fp) < 0) return (-1);
- t->f &= ~TOF_FRESHLINE;
- }
- if (putc(ch, t->fp) < 0) return (-1);
- if (ch == '\n') t->f |= TOF_FRESHLINE;
- return (1);
-}
+ { struct tap_output *t = go; return (format_char(&t->fmt, ch)); }
static int tap_writem(void *go, const char *p, size_t sz)
-{
- struct tap_output *t = go;
- const char *q, *l = p + sz;
- size_t n;
-
- if (p == l) return (0);
- if (t->f&TOF_FRESHLINE)
- if (fputs("## ", t->fp) < 0) return (-1);
- for (;;) {
- q = memchr(p, '\n', l - p); if (!q) break;
- n = q + 1 - p; if (fwrite(p, 1, n, t->fp) < n) return (-1);
- p = q + 1;
- if (p == l) { t->f |= TOF_FRESHLINE; return (sz); }
- if (fputs("## ", t->fp) < 0) return (-1);
- }
- n = l - p; if (fwrite(p, 1, n, t->fp) < n) return (-1);
- t->f &= ~TOF_FRESHLINE; return (0);
-}
+ { struct human_output *t = go; return (format_string(&t->fmt, p, sz)); }
static int tap_nwritef(void *go, size_t maxsz, const char *p, ...)
{
- struct tap_output *t = go;
+ struct human_output *t = go;
+ size_t n;
va_list ap;
- int n;
-
- va_start(ap, p); DRESET(&t->d); DENSURE(&t->d, maxsz + 1);
-#ifdef HAVE_SNPRINTF
- n = vsnprintf(t->d.buf, maxsz + 1, p, ap);
-#else
- n = vsprintf(t->d.buf, p, ap);
-#endif
- assert(0 <= n && n <= maxsz);
+
+ va_start(ap, p);
+ n = gprintf_memputf(&t->outbuf, &t->outsz, maxsz, p, ap);
va_end(ap);
- return (tap_writem(t, t->d.buf, n));
+ return (format_string(&t->fmt, t->outbuf, n));
}
static const struct gprintf_ops tap_printops =
struct tap_output *t = (struct tap_output *)o;
t->tv = tv;
- fputs("TAP version 13\n", t->fp);
+ fputs("TAP version 13\n", t->fmt.fp);
}
static unsigned tap_grpix(struct tap_output *t)
if (tv->f&TVSF_ERROR) {
fputs("Bail out! "
"Errors found in input; tests may not have run correctly\n",
- t->fp);
+ t->fmt.fp);
return (2);
}
- fprintf(t->fp, "1..%u\n", tap_grpix(t));
+ fprintf(t->fmt.fp, "1..%u\n", tap_grpix(t));
t->tv = 0; return (tv->all[TVOUT_LOSE] ? 1 : 0);
}
{
struct tap_output *t = (struct tap_output *)o;
- fprintf(t->fp, "ok %u %s # SKIP", tap_grpix(t), t->tv->test->name);
- if (excuse) { fputc(' ', t->fp); vfprintf(t->fp, excuse, *ap); }
- fputc('\n', t->fp);
+ fprintf(t->fmt.fp, "ok %u %s # SKIP", tap_grpix(t), t->tv->test->name);
+ if (excuse) { fputc(' ', t->fmt.fp); vfprintf(t->fmt.fp, excuse, *ap); }
+ fputc('\n', t->fmt.fp);
}
static void tap_egroup(struct tvec_output *o)
skip = tv->curr[TVOUT_SKIP];
if (lose) {
- fprintf(t->fp, "not ok %u - %s: FAILED %u/%u",
+ fprintf(t->fmt.fp, "not ok %u - %s: FAILED %u/%u",
grpix, tv->test->name, lose, win + lose);
- if (skip) fprintf(t->fp, " (skipped %u)", skip);
+ if (skip) fprintf(t->fmt.fp, " (skipped %u)", skip);
} else {
- fprintf(t->fp, "ok %u - %s: passed %u", grpix, tv->test->name, win);
- if (skip) fprintf(t->fp, " (skipped %u)", skip);
+ fprintf(t->fmt.fp, "ok %u - %s: passed %u", grpix, tv->test->name, win);
+ if (skip) fprintf(t->fmt.fp, " (skipped %u)", skip);
}
- fputc('\n', t->fp);
+ fputc('\n', t->fmt.fp);
}
static void tap_btest(struct tvec_output *o) { ; }
-static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
+static void tap_outcome(struct tvec_output *o, const char *outcome,
+ const char *detail, va_list *ap)
{
struct tap_output *t = (struct tap_output *)o;
struct tvec_state *tv = t->tv;
- fprintf(t->fp, "## %s:%u: `%s' skipped",
- tv->infile, tv->test_lno, tv->test->name);
- if (excuse) { fputs(": ", t->fp); vfprintf(t->fp, excuse, *ap); }
- fputc('\n', t->fp);
+ gprintf(&tap_printops, t, "%s:%u: `%s' %s",
+ tv->infile, tv->test_lno, tv->test->name, outcome);
+ if (detail) {
+ format_string(&t->fmt, ": ", 2);
+ vgprintf(&tap_printops, t, detail, ap);
+ }
+ format_char(&t->fmt, '\n');
}
+static void tap_skip(struct tvec_output *o, const char *excuse, va_list *ap)
+ { tap_outcome(o, "skipped", excuse, ap); }
static void tap_fail(struct tvec_output *o, const char *detail, va_list *ap)
-{
- struct tap_output *t = (struct tap_output *)o;
- struct tvec_state *tv = t->tv;
-
- fprintf(t->fp, "## %s:%u: `%s' FAILED",
- tv->infile, tv->test_lno, tv->test->name);
- if (detail) { fputs(": ", t->fp); vfprintf(t->fp, detail, *ap); }
- fputc('\n', t->fp);
-}
+ { tap_outcome(o, "FAILED", detail, ap); }
static void tap_dumpreg(struct tvec_output *o,
unsigned disp, const union tvec_regval *rv,
struct tap_output *t = (struct tap_output *)o;
const char *ds = regdisp(disp); int n = strlen(ds) + strlen(rd->name);
- fprintf(t->fp, "## %*s%s %s = ", 10 + t->maxlen - n, "", ds, rd->name);
- if (!rv)
- fprintf(t->fp, "#<unset>\n");
- else {
- t->f &= ~TOF_FRESHLINE;
- rd->ty->dump(rv, rd, 0, &tap_printops, t);
- fputc('\n', t->fp);
- }
+ gprintf(&tap_printops, t, "%*s%s %s = ",
+ 10 + t->maxlen - n, "", ds, rd->name);
+ if (!rv) gprintf(&tap_printops, t, "#<unset>");
+ else rd->ty->dump(rv, rd, 0, &tap_printops, t);
+ format_char(&t->fmt, '\n');
}
static void tap_etest(struct tvec_output *o, unsigned outcome) { ; }
struct tap_output *t = (struct tap_output *)o;
struct tvec_state *tv = t->tv;
- fprintf(t->fp, "## %s: %s: ", tv->test->name, ident);
- t->f &= ~TOF_FRESHLINE; tvec_benchreport(&tap_printops, t, unit, tm);
- fputc('\n', t->fp);
+ gprintf(&tap_printops, t, "%s: %s: ", tv->test->name, ident);
+ tvec_benchreport(&tap_printops, t, unit, tm);
+ format_char(&t->fmt, '\n');
}
-static void tap_report(struct tap_output *t, const char *msg, va_list *ap)
+static void tap_report(struct tap_output *t,
+ const struct gprintf_ops *gops, void *go,
+ const char *msg, va_list *ap)
{
struct tvec_state *tv = t->tv;
- if (tv->infile) fprintf(t->fp, "%s:%u: ", tv->infile, tv->lno);
- vfprintf(t->fp, msg, *ap); fputc('\n', t->fp);
+ if (tv->infile) gprintf(gops, go, "%s:%u: ", tv->infile, tv->lno);
+ gprintf(gops, go, msg, ap); gops->putch(go, '\n');
}
static void tap_error(struct tvec_output *o, const char *msg, va_list *ap)
{
struct tap_output *t = (struct tap_output *)o;
- fputs("Bail out! ", t->fp); tap_report(t, msg, ap);
+
+ fputs("Bail out! ", t->fmt.fp);
+ tap_report(t, &file_printops, t->fmt.fp, msg, ap);
}
static void tap_notice(struct tvec_output *o, const char *msg, va_list *ap)
{
struct tap_output *t = (struct tap_output *)o;
- fputs("## ", t->fp); tap_report(t, msg, ap);
+
+ tap_report(t, &tap_printops, t, msg, ap);
}
static void tap_destroy(struct tvec_output *o)
{
struct tap_output *t = (struct tap_output *)o;
- if (t->fp != stdout && t->fp != stderr) fclose(t->fp);
- dstr_destroy(&t->d);
- xfree(t);
+ destroy_fmt(&t->fmt,
+ t->fmt.fp == stdout || t->fmt.fp == stderr ? 0 : DFF_CLOSE);
+ xfree(t->outbuf); xfree(t);
}
static const struct tvec_outops tap_ops = {
struct tap_output *t;
t = xmalloc(sizeof(*t)); t->_o.ops = &tap_ops;
- dstr_create(&t->d);
- t->f = 0; t->fp = fp;
+ init_fmt(&t->fmt, fp, "## ");
+ t->outbuf = 0; t->outsz = 0;
return (&t->_o);
}
#undef f_any
}
-int tvec_send(struct tvec_state *tv, struct tvec_reomte *r)
+int tvec_send(struct tvec_state *tv, struct tvec_remote *r)
{
kludge64 k; unsigned char lenbuf[8];
const char *p; size_t sz;
return (0);
}
-int tvec_recv(struct tvec_state *tv, struct tvec_reomte *r, buf *b_out)
+int tvec_recv(struct tvec_state *tv, struct tvec_remote *r, buf *b_out)
{
kludge64 k, szmax; unsigned char lenbuf[8];
unsigned char *p;
/*----- The output driver -------------------------------------------------*/
#define SENDPK(ro, pk) \
+ if ((ro)->r.f&TVRF_BROKEN) /* do nothing */; else \
MC_BEFORE(setpk, \
{ buf_reset(&(ro)->r.bout); \
buf_putu16l(&(ro)->r.bout.b, (pk)); }) \
{
struct remote_output *ro = (struct remote_output *)o;
- if (ro->r.f&TVRF_BROKEN) return (-1);
- dbuf_reset(&ro->r.bout);
- buf_putu16l(&ro->r.bout.b, TVPK_ERROR);
- buf_vputstrf16l(&ro->r.bout.b, msg, ap);
- return (tvec_send(ro->_o.tv, &ro->r));
+ SENDPK(ro, pk) buf_vputstrf16l(&ro->r.bout.b, msg, ap);
+ return (ro->r.f&TVRF_BROKEN ? -1 : 0);
}
-static void report(struct tvec_output *o, unsigned pk,
+static void report(struct tvec_output *o, unsigned pk, const char *what,
const char *msg, va_list *ap)
{
if (sendstr(o, pk, msg, ap)) {
- fprintf(stderr, "%s: ", QUIS);
+ fprintf(stderr, "%s %s: ", QUIS, what);
vfprintf(stderr, msg, *ap);
fputc('\n', stderr);
}
}
static void remote_error(struct tvec_output *o, const char *msg, va_list *ap)
- { report(o, TVPK_ERROR, msg, ap); }
+ { report(o, TVPK_ERROR, "ERROR", msg, ap); }
static void remote_notice(struct tvec_output *o,
const char *msg, va_list *ap)
- { report(o, TVPK_NOTICE, msg, ap); }
+ { report(o, TVPK_NOTICE, "notice", msg, ap); }
static void remote_setstatus(struct tvec_ouptut *o, int st)
{
# include "base64.h"
# include "hex.h"
#include "dstr.h"
+#include "maths.h"
#include "tvec.h"
/*----- Preliminary utilities ---------------------------------------------*/
-#ifdef isnan
-# define NANP(x) isnan(x)
-#else
-# define NANP(x) (!((x) == (x)))
-#endif
-
-#ifdef isinf
-# define INFP(x) isinf(x)
-#else
-# define INFP(x) ((x) > DBL_MAX || (x) < DBL_MIN)
-#endif
-
-#ifdef signbit
-# define NEGP(x) signbit(x)
-#else
-# define NEGP(x) ((x) < 0.0)
-#endif
+/* --- @trivial_release@ --- *
+ *
+ * Arguments: @union tvec_regval *rv@ = a register value
+ * @const struct tvec_regdef@ = the register definition
+ *
+ * Returns: ---
+ *
+ * Use: Does nothing. Used for register values which don't retain
+ * resources.
+ */
static void trivial_release(union tvec_regval *rv,
const struct tvec_regdef *rd)
{ ; }
+/*----- Integer utilities -------------------------------------------------*/
+
+/* --- @unsigned_to_buf@, @signed_to_buf@ --- *
+ *
+ * Arguments: @buf *b@ = buffer to write on
+ * @unsigned long u@ or @long i@ = integer to write
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Write @i@ to the buffer, in big-endian (two's-complement, it
+ * signed) format.
+ */
+
+static int unsigned_to_buf(buf *b, unsigned long u)
+ { kludge64 k; ASSIGN64(k, u); return (buf_putk64l(b, k)); }
+
static int signed_to_buf(buf *b, long i)
{
kludge64 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)); }
+/* --- @unsigned_from_buf@, @signed_from_buf@ --- *
+ *
+ * Arguments: @buf *b@ = buffer to write on
+ * @unsigned long *u_out@ or @long *i_out@ = where to put the
+ * result
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Read an integer, in big-endian (two's-complement, if signed)
+ * format, from the buffer.
+ */
static int unsigned_from_buf(buf *b, unsigned long *u_out)
{
*u_out = GET64(unsigned long, k); return (0);
}
+/* --- @hex_width@ --- *
+ *
+ * Arguments: @unsigned long u@ = an integer
+ *
+ * Returns: A suitable number of digits to use in order to display @u@ in
+ * hex. Currently, we select a power of two sufficient to show
+ * the value, but at least 2.
+ */
+
static int hex_width(unsigned long u)
{
int wd;
return (wd/4);
}
+/* --- @format_unsigned_hex@, @format_signed_hex@ --- *
+ *
+ * Arguments: @const struct gprintf_ops *gops@ = print operations
+ * @void *go@ = print destination
+ * @unsigned long u@ or @long i@ = integer to print
+ *
+ * Returns: ---
+ *
+ * Use: Print an unsigned or signed integer in hexadecimal.
+ */
+
+static void format_unsigned_hex(const struct gprintf_ops *gops, void *go,
+ unsigned long u)
+ { gprintf(gops, go, "0x%0*lx", hex_width(u), u); }
+
+static void format_signed_hex(const struct gprintf_ops *gops, void *go,
+ long i)
+{
+ unsigned long u = i >= 0 ? i : -(unsigned long)i;
+ gprintf(gops, go, "%s0x%0*lx", i < 0 ? "-" : "", hex_width(u), u);
+}
+
+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);
+}
+
+/* --- @check_unsigned_range@, @check_signed_range@ --- *
+ *
+ * Arguments: @unsigned long u@ or @long i@ = an integer
+ * @const struct tvec_urange *ur@ or
+ * @const struct tvec_irange *ir@ = range specification,
+ * or null
+ * @struct tvec_state *tv@ = test vector state
+ *
+ * Returns: Zero on success, or @-1@ on error.
+ *
+ * Use: Check that the integer is within bounds. If not, report a
+ * suitable error and return a failure indication.
+ */
+
static int check_signed_range(long i,
const struct tvec_irange *ir,
struct tvec_state *tv)
return (0);
}
+/* --- @chtodig@ --- *
+ *
+ * Arguments: @int ch@ = a character
+ *
+ * Returns: The numeric value of the character as a digit, or @-1@ if
+ * it's not a digit. Letters count as extended digits starting
+ * with value 10; case is not significant.
+ */
+
static int chtodig(int ch)
{
if ('0' <= ch && ch <= '9') return (ch - '0');
else return (-1);
}
+/* --- @parse_unsigned_integer@, @parse_signed_integer@ --- *
+ *
+ * Arguments: @unsigned long *u_out@, @long *i_out@ = where to put the
+ * result
+ * @const char **q_out@ = where to put the end position
+ * @const char *p@ = pointer to the string to parse
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Parse an integer from a string in the test-vector format.
+ * This is mostly extension of the traditional C @strtoul@
+ * format: supported inputs include:
+ *
+ * * NNN -- a decimal number (even if it starts with `0');
+ * * 0xNNN -- hexadecimal;
+ * * 0oNNN -- octal;
+ * * 0bNNN -- binary;
+ * * NNrNNN -- base NN.
+ *
+ * Furthermore, single underscores are permitted internally as
+ * an insignificant digit separator.
+ */
+
static int parse_unsigned_integer(unsigned long *u_out, const char **q_out,
const char *p)
{
int ch, d, r;
const char *q;
unsigned f = 0;
-#define f_implicit 1u
-#define f_digit 2u
-#define f_uscore 4u
-
+#define f_implicit 1u /* implicitly reading base 10 */
+#define f_digit 2u /* read a real digit */
+#define f_uscore 4u /* found an underscore */
+
+ /* Initial setup
+ *
+ * This will deal with the traditional `0[box]...' prefixes. We'll leave
+ * our new `NNr...' syntax for later.
+ */
if (p[0] != '0' || !p[1]) {
d = chtodig(*p); if (0 > d || d >= 10) return (-1);
r = 10; u = d; p++; f |= f_implicit | f_digit;
q = p;
for (;;) {
- ch = *p;
- if (ch == '_') {
- if (f&f_uscore) break;
- p++; f = (f&~f_implicit) | f_uscore;
- }
- else if (ch == 'r' || ch == 'R') {
- if (!(f&f_implicit) || !u || u >= 36) break;
- d = chtodig(p[1]); if (0 > d || d >= u) break;
- r = u; u = d; f = (f&~f_implicit) | f_digit; p += 2; q = p;
- } else {
- d = chtodig(ch);
- if (d < 0 || d >= r) break;
- if (u > ULONG_MAX/r) return (-1);
- u *= r; if (u > ULONG_MAX - d) return (-1);
- u += d; f = (f&~f_uscore) | f_digit; p++; q = p;
+ /* Work through the string a character at a time. */
+
+ ch = *p; switch (ch) {
+
+ case '_':
+ /* An underscore is OK if we haven't just seen one. */
+
+ if (f&f_uscore) goto done;
+ p++; f = (f&~f_implicit) | f_uscore;
+ break;
+
+ case 'r': case 'R':
+ /* An `r' is OK if the number so far is small enough to be a sensible
+ * base, and we're scanning decimal implicitly.
+ */
+
+ if (!(f&f_implicit) || !u || u >= 36) goto done;
+ d = chtodig(p[1]); if (0 > d || d >= u) goto done;
+ r = u; u = d; f = (f&~f_implicit) | f_digit; p += 2; q = p;
+ break;
+
+ default:
+ /* Otherwise we expect a valid digit and accumulate it. */
+ d = chtodig(ch); if (d < 0 || d >= r) goto done;
+ if (u > ULONG_MAX/r) return (-1);
+ u *= r; if (u > ULONG_MAX - d) return (-1);
+ u += d; f = (f&~f_uscore) | f_digit; p++; q = p;
+ break;
}
}
+done:
if (!(f&f_digit)) return (-1);
*u_out = u; *q_out = q; return (0);
unsigned f = 0;
#define f_neg 1u
+ /* Read an initial sign. */
if (*p == '+') p++;
else if (*p == '-') { f |= f_neg; p++; }
+ /* Scan an unsigned number. */
if (parse_unsigned_integer(&u, q_out, p)) return (-1);
+ /* Check for signed overflow and apply the sign. */
if (!(f&f_neg)) {
if (u > LONG_MAX) return (-1);
*i_out = u;
#undef f_neg
}
+/* --- @parse_unsigned@, @parse_signed@ --- *
+ *
+ * Arguments: @unsigned long *u_out@ or @long *i_out@ = where to put the
+ * result
+ * @const char *p@ = string to parse
+ * @const struct tvec_urange *ur@ or
+ * @const struct tvec_irange *ir@ = range specification,
+ * or null
+ * @struct tvec_state *tv@ = test vector state
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Parse and range-check an integer. Unlike @parse_(un)signed_
+ * integer@, these functions check that there's no cruft
+ * following the final digit, and report errors as they find
+ * them rather than leaving that to the caller.
+ */
+
+static int parse_unsigned(unsigned long *u_out, const char *p,
+ const struct tvec_urange *ur,
+ struct tvec_state *tv)
+{
+ unsigned long u;
+ const char *q;
+
+ 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);
+ *u_out = u; return (0);
+}
+
static int parse_signed(long *i_out, const char *p,
const struct tvec_irange *ir,
struct tvec_state *tv)
*i_out = i; return (0);
}
-static int parse_unsigned(unsigned long *u_out, const char *p,
- const struct tvec_urange *ur,
- struct tvec_state *tv)
-{
- unsigned long u;
- const char *q;
+/*----- Floating-point utilities ------------------------------------------*/
- 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);
- *u_out = u; return (0);
-}
+/* --- @eqish_floating_p@ --- *
+ *
+ * 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.
+ */
-static void format_signed_hex(const struct gprintf_ops *gops, void *go,
- long i)
+static int eqish_floating_p(double x, double y,
+ const struct tvec_floatinfo *fi)
{
- unsigned long u = i >= 0 ? i : -(unsigned long)i;
- gprintf(gops, go, "%s0x%0*lx", i < 0 ? "-" : "", hex_width(u), u);
+ double t;
+
+ if (NANP(x)) return (NANP(y)); else if (NANP(y)) return (0);
+ if (INFP(x)) return (x == y); else if (INFP(y)) return (0);
+
+ switch (fi ? fi->f&TVFF_EQMASK : TVFF_EXACT) {
+ case TVFF_EXACT:
+ return (x == y && NEGP(x) == NEGP(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);
+ default:
+ abort();
+ }
}
-static void format_unsigned_hex(const struct gprintf_ops *gops, void *go,
- unsigned long u)
- { gprintf(gops, go, "0x%0*lx", hex_width(u), u); }
+/* --- @format_floating@ --- *
+ *
+ * Arguments: @const struct gprintf_ops *gops@ = print operations
+ * @void *go@ = print destination
+ * @double x@ = number to print
+ *
+ * Returns: ---
+ *
+ * Use: Print a floating-point number, accurately.
+ */
static void format_floating(const struct gprintf_ops *gops, void *go,
double x)
}
}
-static int eqish_floating_p(double x, double y,
- const struct tvec_floatinfo *fi)
-{
- double t;
-
- if (NANP(x)) return (NANP(y)); else if (NANP(y)) return (0);
- if (INFP(x)) return (x == y); else if (INFP(y)) return (0);
-
- switch (fi ? fi->f&TVFF_EQMASK : TVFF_EXACT) {
- case TVFF_EXACT:
- return (x == y && NEGP(x) == NEGP(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);
- default:
- abort();
- }
-}
+/* --- @parse_floating@ --- *
+ *
+ * Arguments: @double *x_out@ = where to put the result
+ * @const char *p@ = string to parse
+ * @const struct tvec_floatinfo *fi@ = floating-point info
+ * @struct tvec_state *tv@ = test vector state
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Parse a floating-point number from a string. Reports any
+ * necessary errors.
+ */
static int parse_floating(double *x_out, const char *p,
const struct tvec_floatinfo *fi,
double x;
int olderr, rc;
+ /* Check for special tokens. */
if (STRCMP(p, ==, "#nan")) {
#ifdef NAN
x = NAN; rc = 0;
tvec_error(tv, "NaN not supported on this system");
rc = -1; goto end;
#endif
- } else if (STRCMP(p, ==, "#inf") ||
- STRCMP(p, ==, "#+inf") ||
- STRCMP(p, ==, "+#inf")) {
+ }
+
+ else if (STRCMP(p, ==, "#inf") ||
+ STRCMP(p, ==, "#+inf") || STRCMP(p, ==, "+#inf")) {
#ifdef INFINITY
x = INFINITY; rc = 0;
#else
tvec_error(tv, "infinity not supported on this system");
rc = -1; goto end;
#endif
- } else if (STRCMP(p, ==, "#-inf") ||
- STRCMP(p, ==, "-#inf")) {
+ }
+
+ else if (STRCMP(p, ==, "#-inf") || STRCMP(p, ==, "-#inf")) {
#ifdef INFINITY
x = -INFINITY; rc = 0;
#else
tvec_error(tv, "infinity not supported on this system");
rc = -1; goto end;
#endif
- } else {
+ }
+
+ /* Check that this looks like a number, so we can exclude `strtod'
+ * recognizing its own non-finite number tokens.
+ */
+ else {
pp = p;
if (*pp == '+' || *pp == '-') pp++;
if (*pp == '.') pp++;
tvec_syntax(tv, *p ? *p : fgetc(tv->fp), "floating-point number");
rc = -1; goto end;
}
+
+ /* Parse the number using the system parser. */
olderr = errno; errno = 0;
x = strtod(p, &q);
if (*q) {
errno = olderr;
}
+ /* Check that the number is acceptable. */
if (NANP(x) && fi && !(fi->f&TVFF_NANOK)) {
tvec_error(tv, "#nan not allowed here");
rc = -1; goto end;
}
+
if (fi && ((!(fi->f&TVFF_NOMIN) && x < fi->min) ||
(!(fi->f&TVFF_NOMAX) && x > fi->max))) {
dstr_puts(&d, "floating-point number ");
tvec_error(tv, "%s", d.buf); rc = -1; goto end;
}
- *x_out = x; rc = 0;
-end:
- dstr_destroy(&d);
- return (rc);
+ /* All done. */
+ *x_out = x; rc = 0;
+end:
+ dstr_destroy(&d);
+ return (rc);
+}
+
+/*----- String utilities --------------------------------------------------*/
+
+/* Special character name table. */
+static const struct chartab {
+ const char *name; /* character name */
+ int ch; /* character value */
+ unsigned f; /* flags: */
+#define CTF_PREFER 1u /* preferred name */
+#define CTF_SHORT 2u /* short name (compact style) */
+} chartab[] = {
+ { "#eof", EOF, CTF_PREFER | CTF_SHORT },
+ { "#nul", '\0', CTF_PREFER },
+ { "#bell", '\a', CTF_PREFER },
+ { "#ding", '\a', 0 },
+ { "#bel", '\a', CTF_SHORT },
+ { "#backspace", '\b', CTF_PREFER },
+ { "#bs", '\b', CTF_SHORT },
+ { "#escape", '\x1b', CTF_PREFER },
+ { "#esc", '\x1b', CTF_SHORT },
+ { "#formfeed", '\f', CTF_PREFER },
+ { "#ff", '\f', CTF_SHORT },
+ { "#newline", '\n', CTF_PREFER },
+ { "#linefeed", '\n', 0 },
+ { "#lf", '\n', CTF_SHORT },
+ { "#nl", '\n', 0 },
+ { "#return", '\r', CTF_PREFER },
+ { "#carriage-return", '\r', 0 },
+ { "#cr", '\r', CTF_SHORT },
+ { "#tab", '\t', CTF_PREFER | CTF_SHORT },
+ { "#horizontal-tab", '\t', 0 },
+ { "#ht", '\t', 0 },
+ { "#vertical-tab", '\v', CTF_PREFER },
+ { "#vt", '\v', CTF_SHORT },
+ { "#space", ' ', 0 },
+ { "#spc", ' ', CTF_SHORT },
+ { "#delete", '\x7f', CTF_PREFER },
+ { "#del", '\x7f', CTF_SHORT },
+ { 0, 0, 0 }
+};
+
+/* --- @find_charname@ --- *
+ *
+ * Arguments: @int ch@ = character to match
+ * @unsigned f@ = flags (@CTF_...@) to match
+ *
+ * Returns: The name of the character, or null if no match is found.
+ *
+ * Use: Looks up a name for a character. Specifically, it returns
+ * the first entry in the @chartab@ table which matches @ch@ and
+ * which has one of the flags @f@ set.
+ */
+
+static const char *find_charname(int ch, unsigned f)
+{
+ const struct chartab *ct;
+
+ for (ct = chartab; ct->name; ct++)
+ if (ct->ch == ch && (ct->f&f)) return (ct->name);
+ return (0);
+}
+
+/* --- @read_charname@ --- *
+ *
+ * Arguments: @int *ch_out@ = where to put the character
+ * @const char *p@ = character name
+ * @unsigned f@ = flags (@TCF_...@)
+ *
+ * Returns: Zero if a match was found, @-1@ if not.
+ *
+ * Use: Looks up a character by name. If @RCF_EOFOK@ is set in @f@,
+ * then the @EOF@ marker can be matched; otherwise it can't.
+ */
+
+#define RCF_EOFOK 1u
+static int read_charname(int *ch_out, const char *p, unsigned f)
+{
+ const struct chartab *ct;
+
+ for (ct = chartab; ct->name; ct++)
+ if (STRCMP(p, ==, ct->name) && ((f&RCF_EOFOK) || ct->ch >= 0))
+ { *ch_out = ct->ch; return (0); }
+ return (-1);
+}
+
+/* --- @format_charesc@ --- *
+ *
+ * Arguments: @const struct gprintf_ops *gops@ = print operations
+ * @void *go@ = print destination
+ * @int ch@ = character to format
+ * @unsigned f@ = flags (@FCF_...@)
+ *
+ * Returns: ---
+ *
+ * Use: Format a character as an escape sequence, possibly as part of
+ * a larger string. If @FCF_BRACE@ is set in @f@, then put
+ * braces around a `\x...' code, so that it's suitable for use
+ * in a longer string.
+ */
+
+#define FCF_BRACE 1u
+static void format_charesc(const struct gprintf_ops *gops, void *go,
+ int ch, unsigned f)
+{
+ switch (ch) {
+ case '\a': gprintf(gops, go, "\\a"); break;
+ case '\b': gprintf(gops, go, "\\b"); break;
+ case '\x1b': gprintf(gops, go, "\\e"); break;
+ case '\f': gprintf(gops, go, "\\f"); break;
+ case '\r': gprintf(gops, go, "\\r"); break;
+ case '\n': gprintf(gops, go, "\\n"); break;
+ case '\t': gprintf(gops, go, "\\t"); break;
+ case '\v': gprintf(gops, go, "\\v"); break;
+ case '\\': gprintf(gops, go, "\\\\"); break;
+ case '\'': gprintf(gops, go, "\\'"); break;
+ case '\0':
+ if (f&FCF_BRACE) gprintf(gops, go, "\\{0}");
+ else gprintf(gops, go, "\\0");
+ break;
+ default:
+ if (f&FCF_BRACE)
+ gprintf(gops, go, "\\x{%0*x}", hex_width(UCHAR_MAX), ch);
+ else
+ gprintf(gops, go, "\\x%0*x", hex_width(UCHAR_MAX), ch);
+ break;
+ }
+}
+
+/* --- @format_char@ --- *
+ *
+ * Arguments: @const struct gprintf_ops *gops@ = print operations
+ * @void *go@ = print destination
+ * @int ch@ = character to format
+ *
+ * Returns: ---
+ *
+ * Use: Format a single character.
+ */
+
+static void format_char(const struct gprintf_ops *gops, void *go, int ch)
+{
+ switch (ch) {
+ case '\\': case '\'': escape:
+ gprintf(gops, go, "'");
+ format_charesc(gops, go, ch, 0);
+ gprintf(gops, go, "'");
+ break;
+ default:
+ if (!isprint(ch)) goto escape;
+ gprintf(gops, go, "'%c'", ch);
+ break;
+ }
+}
+
+/* --- @maybe_format_unsigned_char@, @maybe_format_signed_char@ --- *
+ *
+ * Arguments: @const struct gprintf_ops *gops@ = print operations
+ * @void *go@ = print destination
+ * @unsigned long u@ or @long i@ = an integer
+ *
+ * Returns: ---
+ *
+ * Use: Format a (signed or unsigned) integer as a character, if it's
+ * in range, printing something like `= 'q''. It's assumed that
+ * a comment marker has already been output.
+ */
+
+static void maybe_format_unsigned_char
+ (const struct gprintf_ops *gops, void *go, unsigned long u)
+{
+ const char *p;
+
+ p = find_charname(u, CTF_PREFER);
+ if (p) gprintf(gops, go, " = %s", p);
+ if (u < UCHAR_MAX)
+ { gprintf(gops, go, " = "); format_char(gops, go, u); }
}
-static int convert_hex(char ch, int *v_out)
+static void maybe_format_signed_char
+ (const struct gprintf_ops *gops, void *go, long i)
{
- 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);
+ const char *p;
+
+ p = find_charname(i, CTF_PREFER);
+ if (p) gprintf(gops, go, " = %s", p);
+ if (0 <= i && i < UCHAR_MAX)
+ { gprintf(gops, go, " = "); format_char(gops, go, i); }
}
-static int read_escape(int *ch_out, struct tvec_state *tv)
+/* --- @read_charesc@ --- *
+ *
+ * Arguments: @int *ch_out@ = where to put the result
+ * @struct tvec_state *tv@ = test vector state
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Parse and convert an escape sequence from @tv@'s input
+ * stream, assuming that the initial `\' has already been read.
+ * Reports errors as appropriate.
+ */
+
+static int read_charesc(int *ch_out, struct tvec_state *tv)
{
int ch, i, esc;
unsigned f = 0;
ch = getc(tv->fp);
switch (ch) {
- case EOF: case '\n': tvec_syntax(tv, ch, "string escape");
+
+ /* Things we shouldn't find. */
+ case EOF: case '\n': return (tvec_syntax(tv, ch, "string escape"));
+
+ /* Single-character escapes. */
case '\'': *ch_out = '\''; break;
case '\\': *ch_out = '\\'; break;
case '"': *ch_out = '"'; break;
case 't': *ch_out = '\t'; break;
case 'v': *ch_out = '\v'; break;
+ /* Hex escapes, with and without braces. */
case 'x':
ch = getc(tv->fp);
if (ch == '{') { f |= f_brace; ch = getc(tv->fp); }
else f &= ~f_brace;
- if (convert_hex(ch, &esc))
- return (tvec_syntax(tv, ch, "hex digit"));
+ esc = chtodig(ch);
+ if (esc < 0 || esc >= 16) return (tvec_syntax(tv, ch, "hex digit"));
for (;;) {
- ch = getc(tv->fp); if (convert_hex(ch, &i)) break;
- esc = 8*esc + i;
+ ch = getc(tv->fp); i = chtodig(ch); if (i < 0 || i >= 16) break;
+ esc = 16*esc + i;
if (esc > UCHAR_MAX)
return (tvec_error(tv,
"character code %d out of range", esc));
*ch_out = esc;
break;
+ /* Other things, primarily octal escapes. */
+ case '{':
+ f |= f_brace; ch = getc(tv->fp);
+ /* fall through */
default:
if ('0' <= ch && ch < '8') {
i = 1; esc = ch - '0';
esc = 8*esc + ch - '0';
i++; if (i >= 3) break;
}
+ if (f&f_brace) {
+ ch = getc(tv->fp);
+ if (ch != '}') return (tvec_syntax(tv, ch, "`}'"));
+ }
if (esc > UCHAR_MAX)
return (tvec_error(tv,
"character code %d out of range", esc));
- *ch_out = esc;
+ *ch_out = esc; break;
} else
return (tvec_syntax(tv, ch, "string escape"));
}
+ /* Done. */
return (0);
#undef f_brace
}
+/* --- @read_quoted_string@ --- *
+ *
+ * Arguments: @dstr *d@ = string to write to
+ * @int quote@ = initial quote, `'' or `"'
+ * @struct tvec_state *tv@ = test vector state
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Read the rest of a quoted string into @d@, reporting errors
+ * as appropriate.
+ *
+ * A single-quoted string is entirely literal. A double-quoted
+ * string may contain C-like escapes.
+ */
+
static int read_quoted_string(dstr *d, int quote, struct tvec_state *tv)
{
int ch;
case '\\':
if (quote == '\'') goto ordinary;
ch = getc(tv->fp); if (ch == '\n') { tv->lno++; break; }
- ungetc(ch, tv->fp); if (read_escape(&ch, tv)) return (-1);
+ ungetc(ch, tv->fp); if (read_charesc(&ch, tv)) return (-1);
goto ordinary;
default:
if (ch == quote) goto end;
return (0);
}
-#define FCF_BRACE 1u
-static void format_charesc(const struct gprintf_ops *gops, void *go,
- int ch, unsigned f)
-{
- switch (ch) {
- case '\a': gprintf(gops, go, "\\a"); break;
- case '\b': gprintf(gops, go, "\\b"); break;
- case '\x1b': gprintf(gops, go, "\\e"); break;
- case '\f': gprintf(gops, go, "\\f"); break;
- case '\r': gprintf(gops, go, "\\r"); break;
- case '\n': gprintf(gops, go, "\\n"); break;
- case '\t': gprintf(gops, go, "\\t"); break;
- case '\v': gprintf(gops, go, "\\v"); break;
- case '\'': gprintf(gops, go, "\\'"); break;
- case '"': gprintf(gops, go, "\\\""); break;
- default:
- if (f&FCF_BRACE)
- gprintf(gops, go, "\\x{%0*x}", hex_width(UCHAR_MAX), ch);
- else
- gprintf(gops, go, "\\x%0*x", hex_width(UCHAR_MAX), ch);
- break;
- }
-}
-
-static void format_char(const struct gprintf_ops *gops, void *go, int ch)
-{
- if (ch == EOF)
- gprintf(gops, go, "#eof");
- else if (isprint(ch) && ch != '\'')
- gprintf(gops, go, "'%c'", ch);
- else {
- gprintf(gops, go, "'");
- format_charesc(gops, go, ch, 0);
- gprintf(gops, go, "'");
- }
-}
-
-static void maybe_format_signed_char
- (const struct gprintf_ops *gops, void *go, long i)
-{
- if (i == EOF || (0 <= i && i < UCHAR_MAX))
- { gprintf(gops, go, " = "); format_char(gops, go, i); }
-}
-
-static void maybe_format_unsigned_char
- (const struct gprintf_ops *gops, void *go, unsigned long u)
-{
- if (u < UCHAR_MAX)
- { gprintf(gops, go, " = "); format_char(gops, go, u); }
-}
-
-enum { TVCODE_BARE, TVCODE_HEX, TVCODE_BASE64, TVCODE_BASE32 };
+/* --- @collect_bare@ --- *
+ *
+ * Arguments: @dstr *d@ = string to write to
+ * @struct tvec_state *tv@ = test vector state
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Read barewords and the whitespace between them. Stop when we
+ * encounter something which can't start a bareword.
+ */
static int collect_bare(dstr *d, struct tvec_state *tv)
{
ungetc(ch, tv->fp); if (tvec_nexttoken(tv)) { rc = -1; goto end; }
DPUTC(d, ' '); s = SPACE;
break;
- case '"': case '\'': case '!':
+ case '"': case '\'': case '!': case '#': case ')': case '}': case ']':
if (s == SPACE) { ungetc(ch, tv->fp); goto done; }
goto addch;
case '\\':
return (rc);
}
+/* --- @set_up_encoding@ --- *
+ *
+ * Arguments: @const codec_class **ccl_out@ = where to put the class
+ * @unsigned *f_out@ = where to put the flags
+ * @unsigned code@ = the coding scheme to use (@TVEC_...@)
+ *
+ * Returns: ---
+ *
+ * Use: Helper for @read_compound_string@ below.
+ *
+ * Return the appropriate codec class and flags for @code@.
+ * Leaves @*ccl_out@ null if the coding scheme doesn't have a
+ * backing codec class (e.g., @TVCODE_BARE@).
+ */
+
+enum { TVCODE_BARE, TVCODE_HEX, TVCODE_BASE64, TVCODE_BASE32 };
static void set_up_encoding(const codec_class **ccl_out, unsigned *f_out,
unsigned code)
{
}
}
+/* --- @flush_codec@ --- *
+ *
+ * Arguments: @codec *cdc@ = a codec, or null
+ * @dstr *d@ = output string
+ * @struct tvec_state *tv@ = test vector state
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Helper for @read_compound_string@ below.
+ *
+ * Flush out any final buffered material from @cdc@, and check
+ * that it's in a good state. Frees the codec on success. Does
+ * nothing if @cdc@ is null.
+ */
+
+static int flush_codec(codec *cdc, dstr *d, struct tvec_state *tv)
+{
+ int err;
+
+ if (cdc) {
+ err = cdc->ops->code(cdc, 0, 0, d);
+ if (err)
+ return (tvec_error(tv, "invalid %s sequence end: %s",
+ cdc->ops->c->name, codec_strerror(err)));
+ cdc->ops->destroy(cdc);
+ }
+ return (0);
+}
+
+/* --- @read_compound_string@ --- *
+ *
+ * Arguments: @void **p_inout@ = address of output buffer pointer
+ * @size_t *sz_inout@ = address of buffer size
+ * @unsigned code@ = initial interpretation of barewords
+ * @unsigned f@ = other flags (@RCSF_...@)
+ * @struct tvec_state *tv@ = test vector state
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Parse a compound string, i.e., a sequence of stringish pieces
+ * which might be quoted strings, character names, or barewords
+ * to be decoded accoding to @code@, interspersed with
+ * additional directives.
+ *
+ * If the initial buffer pointer is non-null and sufficiently
+ * large, then it will be reused; otherwise, it is freed and a
+ * fresh, sufficiently large buffer is allocated and returned.
+ */
+
+#define RCSF_NESTED 1u
static int read_compound_string(void **p_inout, size_t *sz_inout,
- unsigned code, struct tvec_state *tv)
+ unsigned code, unsigned f,
+ struct tvec_state *tv)
{
- const codec_class *ccl; unsigned f;
+ const codec_class *ccl; unsigned cdf;
codec *cdc;
dstr d = DSTR_INIT, w = DSTR_INIT;
char *p;
+ const char *q;
+ void *pp = 0; size_t sz;
+ unsigned long n;
int ch, err, rc;
- set_up_encoding(&ccl, &f, code);
- if (tvec_nexttoken(tv)) tvec_syntax(tv, fgetc(tv->fp), "string");
+ set_up_encoding(&ccl, &cdf, code); cdc = 0;
+
+ if (tvec_nexttoken(tv)) return (tvec_syntax(tv, fgetc(tv->fp), "string"));
do {
ch = getc(tv->fp);
- if (ch == '"' || ch == '\'')
- { if (read_quoted_string(&d, ch, tv)) { rc = -1; goto end; } }
- 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);
- rc = -1; goto end;
- }
- set_up_encoding(&ccl, &f, code);
- } else if (ccl) {
- ungetc(ch, tv->fp);
- DRESET(&w);
- if (tvec_readword(tv, &w, ";", "%s-encoded fragment", ccl->name))
- { rc = -1; goto end; }
- 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));
- rc = -1; goto end;
- }
- cdc->ops->destroy(cdc);
- } else switch (code) {
- case TVCODE_BARE:
+ switch (ch) {
+
+ case ')': case ']': case '}':
+ /* Close brackets. Leave these for recursive caller if there is one,
+ * or just complain.
+ */
+
+ if (!(f&RCSF_NESTED))
+ { rc = tvec_syntax(tv, ch, "string"); goto end; }
+ ungetc(ch, tv->fp); goto done;
+
+ case '"': case '\'':
+ /* Quotes. Read a quoted string. */
+
+ if (cdc && flush_codec(cdc, &d, tv)) { rc = -1; goto end; }
+ cdc = 0;
+ if (read_quoted_string(&d, ch, tv)) { rc = -1; goto end; }
+ break;
+
+ case '#':
+ /* A named character. */
+
+ 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");
+ if (read_charname(&ch, w.buf, RCF_EOFOK)) {
+ rc = tvec_error(tv, "unknown character name `%s'", d.buf);
+ goto end;
+ }
+ DPUTC(&d, ch); break;
+
+ case '!':
+ /* A magic keyword. */
+
+ if (cdc && flush_codec(cdc, &d, tv)) { rc = -1; goto end; }
+ cdc = 0;
ungetc(ch, tv->fp);
- if (collect_bare(&d, tv)) goto done;
+ DRESET(&w); tvec_readword(tv, &w, ";", "`!'-keyword");
+
+ /* Change bareword coding system. */
+ if (STRCMP(w.buf, ==, "!bare"))
+ { code = TVCODE_BARE; set_up_encoding(&ccl, &cdf, code); }
+ else if (STRCMP(w.buf, ==, "!hex"))
+ { code = TVCODE_HEX; set_up_encoding(&ccl, &cdf, code); }
+ else if (STRCMP(w.buf, ==, "!base32"))
+ { code = TVCODE_BASE32; set_up_encoding(&ccl, &cdf, code); }
+ else if (STRCMP(w.buf, ==, "!base64"))
+ { code = TVCODE_BASE64; set_up_encoding(&ccl, &cdf, code); }
+
+ /* Repeated substrings. */
+ else if (STRCMP(w.buf, ==, "!repeat")) {
+ if (tvec_nexttoken(tv)) {
+ rc = tvec_syntax(tv, fgetc(tv->fp), "repeat count");
+ goto end;
+ }
+ DRESET(&w);
+ if (tvec_readword(tv, &w, ";{", "repeat count"))
+ { rc = -1; goto end; }
+ if (parse_unsigned_integer(&n, &q, w.buf)) {
+ rc = tvec_error(tv, "invalid repeat count `%s'", w.buf);
+ goto end;
+ }
+ if (*q) { rc = tvec_syntax(tv, *q, "`{'"); goto end; }
+ if (tvec_nexttoken(tv))
+ { rc = tvec_syntax(tv, fgetc(tv->fp), "`{'"); goto end; }
+ ch = getc(tv->fp); if (ch != '{')
+ { rc = tvec_syntax(tv, ch, "`{'"); goto end; }
+ sz = 0;
+ if (read_compound_string(&pp, &sz, code, f | RCSF_NESTED, tv))
+ { rc = -1; goto end; }
+ ch = getc(tv->fp); if (ch != '}')
+ { rc = tvec_syntax(tv, ch, "`}'"); goto end; }
+ if (sz) {
+ if (n > (size_t)-1/sz)
+ { rc = tvec_error(tv, "repeat size out of range"); goto end; }
+ dstr_ensure(&d, n*sz);
+ if (sz == 1)
+ { memset(d.buf + d.len, *(unsigned char *)pp, n); d.len += n; }
+ else
+ for (; n--; d.len += sz) memcpy(d.buf + d.len, pp, sz);
+ }
+ xfree(pp); pp = 0;
+ }
+
+ /* Anything else is an error. */
+ else {
+ tvec_error(tv, "unknown string keyword `%s'", w.buf);
+ rc = -1; goto end;
+ }
break;
+
default:
- abort();
+ /* A bareword. Process it according to the current coding system. */
+
+ switch (code) {
+ case TVCODE_BARE:
+ ungetc(ch, tv->fp);
+ if (collect_bare(&d, tv)) goto done;
+ break;
+ default:
+ assert(ccl);
+ ungetc(ch, tv->fp); DRESET(&w);
+ if (tvec_readword(tv, &w, ";", "%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);
+ if (err) {
+ tvec_error(tv, "invalid %s fragment `%s': %s",
+ ccl->name, w.buf, codec_strerror(err));
+ rc = -1; goto end;
+ }
+ break;
+ }
+ break;
}
} while (!tvec_nexttoken(tv));
done:
+ /* Wrap things up. */
+ if (cdc && flush_codec(cdc, &d, tv)) { rc = -1; goto end; }
+ cdc = 0;
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;
rc = 0;
+
end:
+ /* Clean up any debris. */
+ if (cdc) cdc->ops->destroy(cdc);
+ if (pp) xfree(pp);
dstr_destroy(&d); dstr_destroy(&w);
return (rc);
}
tvrange_u16 = { 0, 65535 },
tvrange_u32 = { 0, 4294967296 };
+/* --- @tvec_claimeq_int@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @long i0, i1@ = two signed integers
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @i0@ and @i1@ are equal, otherwise zero.
+ *
+ * Use: Check that values of @i0@ and @i1@ 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: @i0@ is printed as the output
+ * value and @i1@ is printed as the input reference.
+ */
+
int tvec_claimeq_int(struct tvec_state *tv, long i0, long i1,
const char *file, unsigned lno, const char *expr)
{
return (tvec_claimeq(tv, &tvty_int, 0, file, lno, expr));
}
+/* --- @tvec_claimeq_uint@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @unsigned long u0, u1@ = two unsigned integers
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @u0@ and @u1@ 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_uint(struct tvec_state *tv,
unsigned long u0, unsigned long u1,
const char *file, unsigned lno, const char *expr)
parse_float, dump_float
};
-int tvec_claimeq_float(struct tvec_state *tv,
- double f0, double f1,
- const char *file, unsigned lno,
- const char *expr)
-{
- return (tvec_claimeqish_float(tv, f0, f1, TVFF_EXACT, 0.0,
- file, lno, expr));
-}
+/* --- @tvec_claimeqish_float@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @double f0, f1@ = two floating-point numbers
+ * @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 @f0@ and @u1@ are sufficiently close, otherwise
+ * zero.
+ *
+ * Use: Check that values of @f0@ and @f1@ are sufficiently close.
+ * As for @tvec_claim@ above, a test case is automatically begun
+ * and ended if none is already underway. If the values are
+ * too far apart, then @tvec_fail@ is called, quoting @expr@,
+ * and the mismatched values are dumped: @f0@ is printed as the
+ * output value and @f1@ is printed as the input reference.
+ *
+ * The details for the comparison are as follows.
+ *
+ * * A NaN value matches any other NaN, and nothing else.
+ *
+ * * An infinity matches another infinity of the same sign,
+ * and nothing else.
+ *
+ * * If @f&TVFF_EQMASK@ is @TVFF_EXACT@, then any
+ * representable number matches only itself: in particular,
+ * positive and negative zero are considered distinct.
+ * (This allows tests to check that they land on the correct
+ * side of branch cuts, for example.)
+ *
+ * * If @f&TVFF_EQMASK@ is @TVFF_ABSDELTA@, then %$x$% matches
+ * %$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
+ */
+
int tvec_claimeqish_float(struct tvec_state *tv,
double f0, double f1, unsigned f, double delta,
const char *file, unsigned lno,
return (tvec_claimeq(tv, &tvty_float, &arg, file, lno, expr));
}
+/* --- @tvec_claimeq_float@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @double f0, f1@ = two floating-point numbers
+ * @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.
+ *
+ * Use: Check that values of @f0@ and @f1@ are identical. The
+ * function is exactly equivalent to @tvec_claimeqish_float@
+ * with @f == TVFF_EXACT@.
+ */
+
+int tvec_claimeq_float(struct tvec_state *tv,
+ double f0, double f1,
+ const char *file, unsigned lno,
+ const char *expr)
+{
+ return (tvec_claimeqish_float(tv, f0, f1, TVFF_EXACT, 0.0,
+ file, lno, expr));
+}
+
/*----- Enumerations ------------------------------------------------------*/
#define init_ienum init_int
const struct tvec_ienuminfo tvenum_bool =
{ "bool", bool_assoc, &tvrange_int };
+/* --- @tvec_claimeq_tenum@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const struct tvec_typeenuminfo *ei@ = enumeration type info
+ * @ty t0, t1@ = two values
+ * @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 equal, otherwise zero.
+ *
+ * Use: Check that values of @t0@ and @t1@ 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: @t0@ is printed as the output
+ * value and @t1@ is printed as the input reference.
+ */
+
#define DEFCLAIM(tag, ty, slot) \
int tvec_claimeq_##slot##enum \
(struct tvec_state *tv, \
parse_flags, dump_flags
};
+/* --- @tvec_claimeq_flags@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const struct tvec_flaginfo *fi@ = flags type info
+ * @unsigned long f0, f1@ = two values
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @f0@ and @f1@ are equal, otherwise zero.
+ *
+ * Use: Check that values of @f0@ and @f1@ 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: @f0@ is printed as the output
+ * value and @f1@ is printed as the input reference.
+ */
+
int tvec_claimeq_flags(struct tvec_state *tv,
const struct tvec_flaginfo *fi,
unsigned long f0, unsigned long f1,
/*----- Characters --------------------------------------------------------*/
static int tobuf_char(buf *b, const union tvec_regval *rv,
- const struct tvec_regdef *rd)
+ const struct tvec_regdef *rd)
{
uint32 u;
if (0 <= rv->i && rv->i <= UCHAR_MAX) u = rv->i;
}
static int frombuf_char(buf *b, union tvec_regval *rv,
- const struct tvec_regdef *rd)
+ const struct tvec_regdef *rd)
{
uint32 u;
if (ch == '#') {
ungetc(ch, tv->fp);
if (tvec_readword(tv, &d, ";", "character name")) { rc = -1; goto end; }
- if (STRCMP(d.buf, ==, "#eof"))
- rv->i = EOF;
- else {
+ if (read_charname(&ch, d.buf, RCF_EOFOK)) {
rc = tvec_error(tv, "unknown character name `%s'", d.buf);
goto end;
}
- rc = 0; goto end;
+ if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
+ rv->i = ch; rc = 0; goto end;
}
if (ch == '\'') { f |= f_quote; ch = getc(tv->fp); }
switch (ch) {
+ case ';':
+ if (!(f&f_quote)) { rc = tvec_syntax(tv, ch, "character"); goto end; }
+ goto plain;
+ case '\n':
+ if (f&f_quote)
+ { f &= ~f_quote; ungetc(ch, tv->fp); ch = '\''; goto plain; }
+ case EOF:
+ if (f&f_quote) { f &= ~f_quote; ch = '\''; goto plain; }
+ /* fall through */
case '\'':
- if (!(f&f_quote)) goto plain;
- case EOF: case '\n':
rc = tvec_syntax(tv, ch, "character"); goto end;
case '\\':
- if (read_escape(&ch, tv)) return (-1);
+ if (read_charesc(&ch, tv)) return (-1);
default: plain:
rv->i = ch; break;
}
unsigned style,
const struct gprintf_ops *gops, void *go)
{
- if ((style&TVSF_COMPACT) && isprint(rv->i) && rv->i != '\'')
- gprintf(gops, go, "%c", (int)rv->i);
- else
- format_char(gops, go, rv->i);
+ const char *p;
+ unsigned f = 0;
+#define f_semi 1u
+
+ 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 (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;
+ }
+ }
if (!(style&TVSF_COMPACT)) {
- gprintf(gops, go, " ; = %ld = ", rv->i);
+ if (!(f&f_semi)) gprintf(gops, go, " ;");
+ gprintf(gops, go, " = %ld = ", rv->i);
format_signed_hex(gops, go, rv->i);
}
+
+#undef f_semi
}
const struct tvec_regty tvty_char = {
parse_char, dump_char
};
+/* --- @tvec_claimeq_char@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @int ch0, ch1@ = two character codes
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @ch0@ and @ch1@ are equal, otherwise zero.
+ *
+ * Use: Check that values of @ch0@ and @ch1@ 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: @ch0@ is printed as the output
+ * value and @ch1@ is printed as the input reference.
+ */
+
int tvec_claimeq_char(struct tvec_state *tv, int c0, int c1,
const char *file, unsigned lno, const char *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; }
size_t sz;
p = buf_getmem32l(b, &sz); if (!p) return (-1);
- tvec_allocstring(rv, sz); memcpy(rv->str.p, p, sz);
+ tvec_allocstring(rv, sz); memcpy(rv->str.p, p, sz); rv->str.p[sz] = 0;
return (0);
}
{
if (ur && (ur->min > sz || sz > ur->max))
return (tvec_error(tv,
- "invalid string length %lu; must be in [%lu..%lu]",
+ "invalid string length %lu; must be in [%lu .. %lu]",
(unsigned long)sz, ur->min, ur->max));
return (0);
}
{
void *p = rv->str.p;
- if (read_compound_string(&p, &rv->str.sz, TVCODE_BARE, tv)) return (-1);
+ if (read_compound_string(&p, &rv->str.sz, TVCODE_BARE, 0, tv))
+ return (-1);
rv->str.p = p;
if (check_string_length(rv->str.sz, rd->arg.p, tv)) return (-1);
return (0);
{
void *p = rv->bytes.p;
- if (read_compound_string(&p, &rv->bytes.sz, TVCODE_HEX, tv)) return (-1);
+ if (read_compound_string(&p, &rv->bytes.sz, TVCODE_HEX, 0, tv))
+ return (-1);
rv->bytes.p = p;
if (check_string_length(rv->bytes.sz, rd->arg.p, tv)) return (-1);
return (0);
if (!rv->str.sz) { gprintf(gops, go, "\"\""); return; }
p = (const unsigned char *)rv->str.p; l = p + rv->str.sz;
- if (*p == '!' || *p == ';' || *p == '"' || *p == '\'') goto quote;
+ switch (*p) {
+ case '!': case '#': case ';': case '"': case '\'':
+ case '(': case '{': case '[': case ']': case '}': case ')':
+ f |= f_nonword; break;
+ }
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) { gprintf(gops, go, "\n\t"); goto quote; }
else if (f&f_nonword) goto quote;
- gops->putm(go, (const char *)p, rv->str.sz); return;
+
+ gops->putm(go, (const char *)p, rv->str.sz);
+ return;
quote:
gprintf(gops, go, "\"");
for (q = p; q < l; q++)
if (!isprint(*q) || *q == '"') {
if (p < q) gops->putm(go, (const char *)p, q - p);
- if (*q == '\n' && !(style&TVSF_COMPACT))
- gprintf(gops, go, "\\n\"\t\"");
- else
+ if (*q != '\n' || (style&TVSF_COMPACT))
format_charesc(gops, go, *q, FCF_BRACE);
+ else {
+ if (q + 1 == l) { gprintf(gops, go, "\\n\""); return; }
+ else gprintf(gops, go, "\\n\"\n\t\"");
+ }
+ p = q + 1;
}
if (p < q) gops->putm(go, (const char *)p, q - p);
gprintf(gops, go, "\"");
if (l - p < 16) n = l - p;
else n = 16;
- for (i = 0; i < 16; i++) {
+ for (i = 0; i < n; i++) {
if (i < n) gprintf(gops, go, "%02x", p[i]);
else gprintf(gops, go, " ");
- if (i%4 == 3) gprintf(gops, go, " ");
+ if (i < n - 1 && i%4 == 3) gprintf(gops, go, " ");
}
gprintf(gops, go, " ; ");
if (sz > 16) gprintf(gops, go, "[%0*lx] ", wd, (unsigned long)off);
parse_bytes, dump_bytes
};
+/* --- @tvec_claimeq_string@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *p0@, @size_t sz0@ = first string with length
+ * @const char *p1@, @size_t sz1@ = second string with length
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if the strings at @p0@ and @p1@ are equal, otherwise
+ * zero.
+ *
+ * Use: Check that strings at @p0@ and @p1@ 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: @p0@ is printed as the output
+ * value and @p1@ is printed as the input reference.
+ */
+
int tvec_claimeq_string(struct tvec_state *tv,
const char *p0, size_t sz0,
const char *p1, size_t sz1,
return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
}
+/* --- @tvec_claimeq_strz@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *p0, *p1@ = two strings to compare
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if the strings at @p0@ and @p1@ are equal, otherwise
+ * zero.
+ *
+ * Use: Check that strings at @p0@ and @p1@ are equal, as for
+ * @tvec_claimeq_string@, except that the strings are assumed
+ * null-terminated, so their lengths don't need to be supplied
+ * explicitly.
+ */
+
int tvec_claimeq_strz(struct tvec_state *tv,
const char *p0, const char *p1,
const char *file, unsigned lno, const char *expr)
return (tvec_claimeq(tv, &tvty_string, 0, file, lno, expr));
}
+/* --- @tvec_claimeq_bytes@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const void *p0@, @size_t sz0@ = first string with length
+ * @const void *p1@, @size_t sz1@ = second string with length
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if the strings at @p0@ and @p1@ are equal, otherwise
+ * zero.
+ *
+ * Use: Check that binary strings at @p0@ and @p1@ 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: @p0@ is printed as the output
+ * value and @p1@ is printed as the input reference.
+ */
+
int tvec_claimeq_bytes(struct tvec_state *tv,
const void *p0, size_t sz0,
const void *p1, size_t sz1,
return (tvec_claimeq(tv, &tvty_bytes, 0, file, lno, expr));
}
+/* --- @tvec_allocstring@, @tvec_allocbytes@ --- *
+ *
+ * Arguments: @union tvec_regval *rv@ = register value
+ * @size_t sz@ = required size
+ *
+ * Returns: ---
+ *
+ * Use: Allocated space in a text or binary string register. If the
+ * current register size is sufficient, its buffer is left
+ * alone; otherwise, the old buffer, if any, is freed and a
+ * fresh buffer allocated. These functions are not intended to
+ * be used to adjust a buffer repeatedly, e.g., while building
+ * output incrementally: (a) they will perform badly, and (b)
+ * the old buffer contents are simply discarded if reallocation
+ * is necessary. Instead, use a @dbuf@ or @dstr@.
+ *
+ * The @tvec_allocstring@ function sneakily allocates an extra
+ * byte for a terminating zero. The @tvec_allocbytes@ function
+ * doesn't do this.
+ */
+
+void tvec_allocstring(union tvec_regval *rv, size_t sz)
+{
+ if (rv->str.sz <= sz) { xfree(rv->str.p); rv->str.p = xmalloc(sz + 1); }
+ 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;
+}
+
/*----- Buffer type -------------------------------------------------------*/
static int eq_buffer(const union tvec_regval *rv0,
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 (*q == *unit) {
if (f&f_range) goto rangerr;
- u = t; q += 2; break;
+ u = t; q++; break;
}
}
- if (*q && *q != ';') goto bad;
+ if (*q == 'B') q++;
+ if (*q) goto bad;
if (check_string_length(u, rd->arg.p, tv)) { rc = -1; goto end; }
if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
#define TVEC_GREG(vec, i, regsz) \
((struct tvec_reg *)((unsigned char *)(vec) + (i)*(regsz)))
-/*------ Serialization utilities ------------------------------------------*/
-
-/* --- @tvec_serialize@ --- *
- *
- * Arguments: @const struct tvec_reg *rv@ = vector of registers
- * @buf *b@ = buffer to write on
- * @const struct tvec_regdef *regs@ = vector of register
- * descriptions, terminated by an entry with a null
- * @name@ slot
- * @unsigned nr@ = number of entries in the @rv@ vector
- * @size_t regsz@ = true size of each element of @rv@
- *
- * Returns: Zero on success, @-1@ on failure.
- *
- * Use: Serialize a collection of register values.
- *
- * The `candidate register definitions' are those entries @r@ in
- * the @regs@ vector whose index @r.i@ is strictly less than
- * @nr@. The `selected register definitions' are those
- * candidate register definitions @r@ for which the indicated
- * register @rv[r.i]@ has the @TVRF_LIVE@ flag set. The
- * serialized output begins with a header bitmap: if there are
- * %$n$% candidate register definitions then the header bitmap
- * consists of %$\lceil n/8 \rceil$% bytes. Bits are ordered
- * starting from the least significant bit of the first byte,
- * end ending at the most significant bit of the final byte.
- * The bit corresponding to a candidate register definition is
- * set if and only if that register defintion is selected. The
- * header bitmap is then followed by the serializations of the
- * selected registers -- i.e., for each selected register
- * definition @r@, the serialized value of register @rv[r.i]@ --
- * simply concatenated together, with no padding or alignment.
- *
- * The serialized output is written to the buffer @b@. Failure
- * can be caused by running out of buffer space, or a failing
- * type handler.
- */
-
-extern int tvec_serialize(const struct tvec_reg */*rv*/, buf */*b*/,
- const struct tvec_regdef */*regs*/,
- unsigned /*nr*/, size_t /*regsz*/);
-
-/* --- @tvec_deserialize@ --- *
- *
- * Arguments: @struct tvec_reg *rv@ = vector of registers
- * @buf *b@ = buffer to write on
- * @const struct tvec_regdef *regs@ = vector of register
- * descriptions, terminated by an entry with a null
- * @name@ slot
- * @unsigned nr@ = number of entries in the @rv@ vector
- * @size_t regsz@ = true size of each element of @rv@
- *
- * Returns: Zero on success, @-1@ on failure.
- *
- * Use: Deserialize a collection of register values.
- *
- * The size of the register vector @nr@ and the register
- * definitions @regs@ must match those used when producing the
- * serialization. For each serialized register value,
- * deserialize and store the value into the appropriate register
- * slot, and set the @TVRF_LIVE@ flag on the register. See
- * @tvec_serialize@ for a description of the format.
- *
- * On successful completion, store the address of the first byte
- * after the serialized data in @*end_out@ if @end_out@ is not
- * null. Failure results only from a failing register type
- * handler.
- */
-
-extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/,
- const struct tvec_regdef */*regs*/,
- unsigned /*nr*/, size_t /*regsz*/);
-
/*----- Test state --------------------------------------------------------*/
enum {
*/
#define TVEC_REG(tv, vec, i) TVEC_GREG((tv)->vec, (i), (tv)->regsz)
+struct tvec_config {
+ /* An overall test configuration. */
+
+ const struct tvec_test *tests; /* the tests to be performed */
+ unsigned nrout, nreg; /* number of output/total regs */
+ size_t regsz; /* size of a register */
+};
+
+/*----- Output formatting -------------------------------------------------*/
+
+struct tvec_output {
+ /* An output formatter. */
+ const struct tvec_outops *ops; /* pointer to operations */
+};
+
+/* Benchmarking details. */
+enum {
+ TVBU_OP, /* counting operations of some kind */
+ TVBU_BYTE /* counting bytes (@rbuf >= 0@) */
+};
+struct bench_timing; /* forward declaration */
+
+struct tvec_outops {
+ /* Output operations. */
+
+ void (*bsession)(struct tvec_output */*o*/, struct tvec_state */*tv*/);
+ /* Begin a test session. The output driver will probably want to
+ * save @tv@, because this isn't provided to any other methods.
+ */
+
+ int (*esession)(struct tvec_output */*o*/);
+ /* End a session, and return the suggested exit code. */
+
+ void (*bgroup)(struct tvec_output */*o*/);
+ /* Begin a test group. The test group description is @tv->test@. */
+
+ void (*skipgroup)(struct tvec_output */*o*/,
+ const char */*excuse*/, va_list */*ap*/);
+ /* The group is being skipped; @excuse@ may be null or a format
+ * string explaining why. The @egroup@ method will not be called
+ * separately.
+ */
+
+ void (*egroup)(struct tvec_output */*o*/);
+ /* End a test group. At least one test was attempted or @skipgroup@
+ * would have been called instead. If @tv->curr[TVOUT_LOSE]@ is
+ * nonzero then the test group as a whole failed; otherwise it
+ * passed.
+ */
+
+ void (*btest)(struct tvec_output */*o*/);
+ /* Begin a test case. */
+
+ void (*skip)(struct tvec_output */*o*/,
+ const char */*excuse*/, va_list */*ap*/);
+ /* The test case is being skipped; @excuse@ may be null or a format
+ * string explaining why. The @etest@ function will still be called
+ * (so this works differently from @skipgroup@ and @egroup@). A test
+ * case can be skipped at most once, and, if skipped, it cannot fail.
+ */
+
+ void (*fail)(struct tvec_output */*o*/,
+ const char */*detail*/, va_list */*ap*/);
+ /* The test case failed.
+ *
+ * The output driver should preferably report the filename (@infile@)
+ * and line number (@test_lno@, not @lno@) for the failing test.
+ *
+ * The @detail@ may be null or a format string describing detail
+ * about how the failing test was run which can't be determined from
+ * the registers; a @detail@ is usually provided when (and only when)
+ * the test environment potentially invokes the test function more
+ * than once.
+ *
+ * A single test case can fail multiple times!
+ */
+
+ void (*dumpreg)(struct tvec_output */*o*/,
+ unsigned /*disp*/, const union tvec_regval */*rv*/,
+ const struct tvec_regdef */*rd*/);
+ /* Dump a register. The `disposition' @disp@ explains what condition
+ * the register is in; see @tvec_dumpreg@ and the @TVRD_...@ codes.
+ * The register value is at @rv@, and its definition, including its
+ * type, at @rd@. Note that this function may be called on virtual
+ * registers which aren't in either of the register vectors or
+ * mentioned by the test description.
+ */
+
+ void (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/);
+ /* The test case concluded with the given @outcome@ (one of the
+ * @TVOUT_...@ codes.
+ */
+
+ void (*bbench)(struct tvec_output */*o*/,
+ const char */*ident*/, unsigned /*unit*/);
+ /* Begin a benchmark. The @ident@ is a formatted string identifying
+ * the benchmark based on the values of the input registered marked
+ * @TVRF_ID@; the output driver is free to use this or come up with
+ * its own way to identify the test, e.g., by inspecting the register
+ * values for itself. The @unit@ is one of the @TVBU_...@ constants
+ * explaining what sort of thing is being measured.
+ */
+
+ void (*ebench)(struct tvec_output */*o*/,
+ const char */*ident*/, unsigned /*unit*/,
+ const struct bench_timing */*tm*/);
+ /* End a benchmark. The @ident@ and @unit@ arguments are as for
+ * @bbench@. If @tm@ is zero then the measurement failed; otherwise
+ * @tm->n@ counts total number of things (operations or bytes, as
+ * indicated by @unit@) processed in the indicated time.
+ */
+
+ void (*error)(struct tvec_output */*o*/,
+ const char */*msg*/, va_list */*ap*/);
+ /* Report an error. The driver should ideally report the filename
+ * (@infile@) and line number (@lno@) prompting the error.
+ */
+
+ void (*notice)(struct tvec_output */*o*/,
+ const char */*msg*/, va_list */*ap*/);
+ /* Report a miscellaneous message. The driver should ideally report
+ * the filename (@infile@) and line number (@lno@) prompting the
+ * error.
+ */
+
+ void (*destroy)(struct tvec_output */*o*/);
+ /* Release any resources acquired by the driver. */
+};
+
/*----- Test descriptions -------------------------------------------------*/
typedef void tvec_testfn(const struct tvec_reg */*in*/,
int (*setup)(struct tvec_state */*tv*/, const struct tvec_env */*env*/,
void */*pctx*/, void */*ctx*/);
- /* Initialize the context; called at the start of a test group. Return
- * zero on success, or @-1@ on failure. If setup fails, the context is
- * freed, and the test group is skipped.
+ /* Initialize the context; called at the start of a test group; @pctx@ is
+ * null for environments called by the core, but may be non-null for
+ * subordinate environments. Return zero on success, or @-1@ on failure.
+ * If setup fails, the context is freed, and the test group is skipped.
*/
int (*set)(struct tvec_state */*tv*/, const char */*var*/,
TVRD_EXPECT /* mismatching input register */
};
-/* --- @tvec_skipgroup@, @tvec_skipgroup_v@ --- *
- *
- * Arguments: @struct tvec_state *tv@ = test-vector state
- * @const char *excuse@, @va_list *ap@ = reason why skipped
- *
- * Returns: ---
- *
- * Use: Skip the current group. This should only be called from a
- * test environment @setup@ function; a similar effect occurs if
- * the @setup@ function fails.
- */
-
-extern void PRINTF_LIKE(2, 3)
- tvec_skipgroup(struct tvec_state */*tv*/, const char */*excuse*/, ...);
-extern void tvec_skipgroup_v(struct tvec_state */*tv*/,
- const char */*excuse*/, va_list */*ap*/);
-
-/* --- @tvec_skip@, @tvec_skip_v@ --- *
- *
- * Arguments: @struct tvec_state *tv@ = test-vector state
- * @const char *excuse@, @va_list *ap@ = reason why test skipped
- *
- * Returns: ---
- *
- * Use: Skip the current test. This should only be called from a
- * test environment @run@ function; a similar effect occurs if
- * the @before@ function fails.
- */
+/*----- Register types ----------------------------------------------------*/
-extern void PRINTF_LIKE(2, 3)
- tvec_skip(struct tvec_state */*tv*/, const char */*excuse*/, ...);
-extern void tvec_skip_v(struct tvec_state */*tv*/,
- const char */*excuse*/, va_list */*ap*/);
+struct tvec_regty {
+ /* A register type. */
-/* --- @tvec_resetoutputs@ --- *
- *
- * Arguments: @struct tvec_state *tv@ = test-vector state
- *
- * Returns: ---
- *
- * Use: Reset (releases and reinitializes) the output registers in
- * the test state. This is mostly of use to test environment
- * @run@ functions, between invocations of the test function.
- */
+ void (*init)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/);
+ /* Initialize the value in @*rv@. This will be called before any
+ * other function acting on the value, including @release@.
+ */
-extern void tvec_resetoutputs(struct tvec_state */*tv*/);
+ void (*release)(union tvec_regval */*rv*/,
+ const struct tvec_regdef */*rd*/);
+ /* Release any resources associated with the value in @*rv@. */
-/* --- @tvec_checkregs@ --- *
- *
+ int (*eq)(const union tvec_regval */*rv0*/,
+ const union tvec_regval */*rv1*/,
+ const struct tvec_regdef */*rd*/);
+ /* Return nonzero if @*rv0@ and @*rv1@ are equal values.
+ * Asymmetric criteria are permitted: @tvec_checkregs@ calls @eq@
+ * with the input register as @rv0@ and the output as @rv1@.
+ */
+
+ int (*tobuf)(buf */*b*/, const union tvec_regval */*rv*/,
+ const struct tvec_regdef */*rd*/);
+ /* Serialize the value @*rv@, writing the result to @b@. Return
+ * zero on success, or @-1@ on error.
+ */
+
+ int (*frombuf)(buf */*b*/, union tvec_regval */*rv*/,
+ const struct tvec_regdef */*rd*/);
+ /* Deserialize a value from @b@, storing it in @*rv@. Return zero on
+ * success, or @-1@ on error.
+ */
+
+ int (*parse)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/,
+ struct tvec_state */*tv*/);
+ /* Parse a value from @tv->fp@, storing it in @*rv@. Return zero on
+ * success, or @-1@ on error, having reported one or more errors via
+ * @tvec_error@ or @tvec_syntax@. A successful return should leave
+ * the input position at the start of the next line; the caller will
+ * flush the remainder of the line itself.
+ */
+
+ void (*dump)(const union tvec_regval */*rv*/,
+ const struct tvec_regdef */*rd*/,
+ unsigned /*style*/,
+ const struct gprintf_ops */*gops*/, void */*go*/);
+#define TVSF_COMPACT 1u
+ /* Write a human-readable representation of the value @*rv@ using
+ * @gprintf@ on @gops@ and @go@. The @style@ is a collection of
+ * flags: if @TVSF_COMPACT@ is set, then output should be minimal,
+ * and must fit on a single line; otherwise, output may consist of
+ * multiple lines and may contain redundant information if that is
+ * likely to be useful to a human reader.
+ */
+};
+
+/*----- Session lifecycle -------------------------------------------------*/
+
+/* Here's the overall flow for a testing session.
+ *
+ * @tvec_begin@
+ * -> output @bsession@
+ * @tvec_read@
+ * -> output @bgroup@
+ * -> env @setup@
+ * one or more tests
+ * -> type @init@ (input and output)
+ * -> type @parse@ (input)
+ * -> output @btest@
+ * -> env @before@
+ * -> env @run@
+ * -> @tvec_skip@
+ * -> output @skip@
+ * -> test @fn@
+ * -> @tvec_checkregs@
+ * -> type @eq@
+ * -> @tvec_fail@
+ * -> output @fail@
+ * -> @tvec_mismatch@
+ * -> output @dumpreg@
+ * -> type @dump@
+ * -> env @after@
+ * -> output @etest@
+ * or
+ * -> output @skipgroup@
+ * finally
+ * -> output @egroup@
+ * -> env @teardown@
+ *
+ * @tvec_adhoc@
+ * @tvec_begingroup@
+ * -> output @bgroup@
+ * -> env @setup@
+ * @tvec_begintest@
+ * -> output @btest@
+ * @tvec_skip@
+ * -> output @skip@
+ * @tvec_claimeq@
+ * -> @tvec_fail@
+ * -> output @fail@
+ * -> @tvec_mismatch@
+ * -> output @dumpreg@
+ * -> type @dump@
+ * @tvec_endtest@
+ * -> output @etest@
+ * or @tvec_skipgroup@
+ * -> output @skipgroup@
+ * @tvec_endgroup@
+ * -> output @egroup@
+ *
+ * @tvec_end@
+ * -> output @esession@
+ * -> output @destroy@
+ *
+ * @tvec_benchrun@
+ * -> type @dump@ (compact style)
+ * -> output @bbench@
+ * -> subenv @run@
+ * -> test @fn@
+ * -> output @ebench@
+ * -> @tvec_benchreport@
+ *
+ * The output functions @error@ and @notice@ can be called at arbitrary
+ * times.
+ */
+
+/* --- @tvec_begin@ --- *
+ *
+ * Arguments: @struct tvec_state *tv_out@ = state structure to fill in
+ * @const struct tvec_config *config@ = test configuration
+ * @struct tvec_output *o@ = output driver
+ *
+ * Returns: ---
+ *
+ * Use: Initialize a state structure ready to do some testing.
+ */
+
+extern void tvec_begin(struct tvec_state */*tv_out*/,
+ const struct tvec_config */*config*/,
+ struct tvec_output */*o*/);
+
+/* --- @tvec_end@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns: A proposed exit code.
+ *
+ * Use: Conclude testing and suggests an exit code to be returned to
+ * the calling program. (The exit code comes from the output
+ * driver's @esession@ method.)
+ */
+
+extern int tvec_end(struct tvec_state */*tv*/);
+
+/* --- @tvec_read@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *infile@ = the name of the input file
+ * @FILE *fp@ = stream to read from
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Read test vector data from @fp@ and exercise test functions.
+ * THe return code doesn't indicate test failures: it's only
+ * concerned with whether there were problems with the input
+ * file or with actually running the tests.
+ */
+
+extern int tvec_read(struct tvec_state */*tv*/,
+ const char */*infile*/, FILE */*fp*/);
+
+/*----- Command-line interface --------------------------------------------*/
+
+extern const struct tvec_config tvec_adhocconfig;
+/* A special @struct tvec_config@ to use for programs which perform ad-hoc
+ * testing.
+ */
+
+/* --- @tvec_parseargs@ --- *
+ *
+ * Arguments: @int argc@ = number of command-line arguments
+ * @char *argv[]@ = vector of argument strings
+ * @struct tvec_state *tv_out@ = test vector state to initialize
+ * @int *argpos_out@ = where to leave unread argument index
+ * @const struct tvec_config *cofig@ = test vector configuration
+ *
+ * Returns: ---
+ *
+ * Use: Parse arguments and set up the test vector state @*tv_out@.
+ * If errors occur, print messages to standard error and exit
+ * with status 2.
+ */
+
+extern void tvec_parseargs(int /*argc*/, char */*argv*/[],
+ struct tvec_state */*tv_out*/,
+ int */*argpos_out*/,
+ const struct tvec_config */*config*/);
+
+/* --- @tvec_readstdin@, @tvec_readfile@, @tvec_readarg@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @const char *file@ = pathname of file to read
+ * @const char *arg@ = argument to interpret
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Read test vector data from stdin or a named file. The
+ * @tvec_readarg@ function reads from stdin if @arg@ is `%|-|%',
+ * and from the named file otherwise.
+ */
+
+extern int tvec_readstdin(struct tvec_state */*tv*/);
+extern int tvec_readfile(struct tvec_state */*tv*/, const char */*file*/);
+extern int tvec_readarg(struct tvec_state */*tv*/, const char */*arg*/);
+
+/* --- @tvec_readdflt@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @const char *dflt@ = defsault filename or null
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Reads from the default test vector data. If @file@ is null,
+ * then read from standard input, unless that's a terminal; if
+ * @file@ is not null, then read the named file, looking in the
+ * directory named by the `%|srcdir|%' environment variable if
+ * that's set, or otherwise in the current directory.
+ */
+
+extern int tvec_readdflt(struct tvec_state */*tv*/, const char */*file*/);
+
+/* --- @tvec_readargs@ --- *
+ *
+ * Arguments: @int argc@ = number of command-line arguments
+ * @char *argv[]@ = vector of argument strings
+ * @struct tvec_state *tv@ = test vector state
+ * @int *argpos_inout@ = current argument position (updated)
+ * @const char *dflt@ = default filename or null
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Reads from the sources indicated by the command-line
+ * arguments, in order, interpreting each as for @tvec_readarg@;
+ * if no arguments are given then read from @dflt@ as for
+ * @tvec_readdflt@.
+ */
+
+extern int tvec_readargs(int /*argc*/, char */*argv*/[],
+ struct tvec_state */*tv*/,
+ int */*argpos_inout*/, const char */*dflt*/);
+
+/* --- @tvec_main@ --- *
+ *
+ * Arguments: @int argc@ = number of command-line arguments
+ * @char *argv[]@ = vector of argument strings
+ * @const struct tvec_config *cofig@ = test vector configuration
+ * @const char *dflt@ = default filename or null
+ *
+ * Returns: Exit code.
+ *
+ * Use: All-in-one test vector front-end. Parse options from the
+ * command-line as for @tvec_parseargs@, and then process the
+ * remaining positional arguments as for @tvec_readargs@. The
+ * function constructs and disposes of a test vector state.
+ */
+
+extern int tvec_main(int /*argc*/, char */*argv*/[],
+ const struct tvec_config */*config*/,
+ const char */*dflt*/);
+
+/*----- Test processing ---------------------------------------------------*/
+
+/* --- @tvec_skipgroup@, @tvec_skipgroup_v@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *excuse@, @va_list *ap@ = reason why skipped
+ *
+ * Returns: ---
+ *
+ * Use: Skip the current group. This should only be called from a
+ * test environment @setup@ function; a similar effect occurs if
+ * the @setup@ function fails.
+ */
+
+extern void PRINTF_LIKE(2, 3)
+ tvec_skipgroup(struct tvec_state */*tv*/, const char */*excuse*/, ...);
+extern void tvec_skipgroup_v(struct tvec_state */*tv*/,
+ const char */*excuse*/, va_list */*ap*/);
+
+/* --- @tvec_skip@, @tvec_skip_v@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *excuse@, @va_list *ap@ = reason why test skipped
+ *
+ * Returns: ---
+ *
+ * Use: Skip the current test. This should only be called from a
+ * test environment @run@ function; a similar effect occurs if
+ * the @before@ function fails.
+ */
+
+extern void PRINTF_LIKE(2, 3)
+ tvec_skip(struct tvec_state */*tv*/, const char */*excuse*/, ...);
+extern void tvec_skip_v(struct tvec_state */*tv*/,
+ const char */*excuse*/, va_list */*ap*/);
+
+/* --- @tvec_resetoutputs@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns: ---
+ *
+ * Use: Reset (releases and reinitializes) the output registers in
+ * the test state. This is mostly of use to test environment
+ * @run@ functions, between invocations of the test function.
+ */
+
+extern void tvec_resetoutputs(struct tvec_state */*tv*/);
+
+/* --- @tvec_checkregs@ --- *
+ *
* Arguments: @struct tvec_state *tv@ = test-vector state
*
* Returns: Zero on success, @-1@ on mismatch.
extern void tvec_check_v(struct tvec_state */*tv*/,
const char */*detail*/, va_list */*ap*/);
-/*----- Session lifecycle -------------------------------------------------*/
-
-struct tvec_config {
- /* An overall test configuration. */
-
- const struct tvec_test *tests; /* the tests to be performed */
- unsigned nrout, nreg; /* number of output/total regs */
- size_t regsz; /* size of a register */
-};
+/*----- Ad-hoc testing ----------------------------------------------------*/
-/* --- @tvec_begin@ --- *
+/* --- @tvec_adhoc@ --- *
*
- * Arguments: @struct tvec_state *tv_out@ = state structure to fill in
- * @const struct tvec_config *config@ = test configuration
- * @struct tvec_output *o@ = output driver
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @struct tvec_test *t@ = space for a test definition
*
* Returns: ---
*
- * Use: Initialize a state structure ready to do some testing.
+ * Use: Begin ad-hoc testing, i.e., without reading a file of
+ * test-vector data.
+ *
+ * The structure at @t@ will be used to record information about
+ * the tests underway, which would normally come from a static
+ * test definition. The other functions in this section assume
+ * that @tvec_adhoc@ has been called.
*/
-extern void tvec_begin(struct tvec_state */*tv_out*/,
- const struct tvec_config */*config*/,
- struct tvec_output */*o*/);
+extern void tvec_adhoc(struct tvec_state */*tv*/, struct tvec_test */*t*/);
-/* --- @tvec_end@ --- *
+/* --- @tvec_begingroup@, @TVEC_BEGINGROUP@ --- *
*
* Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *name@ = name for this test group
+ * @const char *file@, @unsigned @lno@ = calling file and line
*
- * Returns: A proposed exit code.
+ * Returns: ---
*
- * Use: Conclude testing and suggests an exit code to be returned to
- * the calling program. (The exit code comes from the output
- * driver's @esession@ method.)
+ * Use: Begin an ad-hoc test group with the given name. The @file@
+ * and @lno@ can be anything, but it's usually best if they
+ * refer to the source code performing the test: the macro
+ * @TVEC_BEGINGROUP@ does this automatically.
*/
-extern int tvec_end(struct tvec_state */*tv*/);
+extern void tvec_begingroup(struct tvec_state */*tv*/, const char */*name*/,
+ const char */*file*/, unsigned /*lno*/);
+#define TVEC_BEGINGROUP(tv, name) \
+ do tvec_begingroup(tv, name, __FILE__, __LINE__); while (0)
-/* --- @tvec_read@ --- *
+/* --- @tvec_endgroup@ --- *
*
* Arguments: @struct tvec_state *tv@ = test-vector state
- * @const char *infile@ = the name of the input file
- * @FILE *fp@ = stream to read from
*
- * Returns: Zero on success, @-1@ on error.
+ * Returns: ---
*
- * Use: Read test vector data from @fp@ and exercise test functions.
- * THe return code doesn't indicate test failures: it's only
- * concerned with whether there were problems with the input
- * file or with actually running the tests.
- */
-
-extern int tvec_read(struct tvec_state */*tv*/,
- const char */*infile*/, FILE */*fp*/);
-
-/*----- Input utilities ---------------------------------------------------*/
-
-/* These are provided by the core for the benefit of type @parse@ methods,
- * and test-environment @set@ functions, which get to read from the test
- * input file. The latter are usually best implemented by calling on the
- * former.
- *
- * The two main rules are as follows.
- *
- * * Leave the file position at the beginning of the line following
- * whatever it was that you read.
- *
- * * When you read and consume a newline (which you do at least once, by
- * the previous rule), then increment @tv->lno@ to keep track of the
- * current line number.
- */
-
-/* --- @tvec_skipspc@ --- *
- *
- * Arguments: @struct tvec_state *tv@ = test-vector state
- *
- * Returns: ---
- *
- * Use: Advance over any whitespace characters other than newlines.
- * This will stop at `;', end-of-file, or any other kind of
- * non-whitespace; and it won't consume a newline.
- */
-
-extern void tvec_skipspc(struct tvec_state */*tv*/);
-
-/* --- @tvec_syntax@, @tvec_syntax_v@ --- *
- *
- * Arguments: @struct tvec_state *tv@ = test-vector state
- * @int ch@ = the character found (in @fgetc@ format)
- * @const char *expect@, @va_list ap@ = what was expected
- *
- * Returns: @-1@
- *
- * Use: Report a syntax error quoting @ch@ and @expect@. If @ch@ is
- * a newline, then back up so that it can be read again (e.g.,
- * by @tvec_flushtoeol@ or @tvec_nexttoken@, which will also
- * advance the line number).
- */
-
-extern int PRINTF_LIKE(3, 4)
- tvec_syntax(struct tvec_state */*tv*/, int /*ch*/,
- const char */*expect*/, ...);
-extern int tvec_syntax_v(struct tvec_state */*tv*/, int /*ch*/,
- const char */*expect*/, va_list */*ap*/);
-
-/* --- @tvec_flushtoeol@ --- *
- *
- * Arguments: @struct tvec_state *tv@ = test-vector state
- * @unsigned f@ = flags (@TVFF_...@)
- *
- * Returns: Zero on success, @-1@ on error.
- *
- * Use: Advance to the start of the next line, consuming the
- * preceding newline.
- *
- * A syntax error is reported if no newline character is found,
- * i.e., the file ends in mid-line. A syntax error is also
- * reported if material other than whitespace or a comment is
- * found before the end of the line end, and @TVFF_ALLOWANY@ is
- * not set in @f@. The line number count is updated
- * appropriately.
- */
-
-#define TVFF_ALLOWANY 1u
-extern int tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/);
-
-/* --- @tvec_nexttoken@ --- *
- *
- * Arguments: @struct tvec_state *tv@ = test-vector state
- *
- * Returns: Zero if there is a next token which can be read; @-1@ if no
- * token is available.
- *
- * Use: Advance to the next whitespace-separated token, which may be
- * on the next line.
- *
- * Tokens are separated by non-newline whitespace, comments, and
- * newlines followed by whitespace; a newline /not/ followed by
- * whitespace instead begins the next assignment, and two
- * newlines separated only by whitespace terminate the data for
- * a test.
- *
- * If this function returns zero, then the next character in the
- * file begins a suitable token which can be read and
- * processed. If it returns @-1@ then there is no such token,
- * and the file position is left correctly. The line number
- * count is updated appropriately.
- */
-
-extern int tvec_nexttoken(struct tvec_state */*tv*/);
-
-/* --- @tvec_readword@ --- *
- *
- * Arguments: @struct tvec_state *tv@ = test-vector state
- * @dstr *d@ = string to append the word to
- * @const char *delims@ = additional delimiters to stop at
- * @const char *expect@, @va_list ap@ = what was expected
- *
- * Returns: Zero on success, @-1@ on failure.
- *
- * Use: A `word' consists of characters other than whitespace, null
- * characters, and other than those listed in @delims@;
- * furthermore, a word does not begin with a `;'. (If you want
- * reading to stop at comments not preceded by whitespace, then
- * include `;' in @delims@. This is a common behaviour.)
- *
- * If there is no word beginning at the current file position,
- * then return @-1@; furthermore, if @expect@ is not null, then
- * report an appropriate error via @tvec_syntax@.
- *
- * Otherwise, the word is accumulated in @d@ and zero is
- * returned; if @d@ was not empty at the start of the call, the
- * newly read word is separated from the existing material by a
- * single space character. Since null bytes are never valid
- * word constituents, a null terminator is written to @d@, and
- * it is safe to treat the string in @d@ as being null-
- * terminated.
- */
-
-extern int PRINTF_LIKE(4, 5)
- tvec_readword(struct tvec_state */*tv*/, dstr */*d*/,
- const char */*delims*/, const char */*expect*/, ...);
-extern int tvec_readword_v(struct tvec_state */*tv*/, dstr */*d*/,
- const char */*delims*/, const char */*expect*/,
- va_list */*ap*/);
-
-/*----- Ad-hoc testing ----------------------------------------------------*/
-
-/* --- @tvec_adhoc@ --- *
- *
- * Arguments: @struct tvec_state *tv@ = test-vector state
- * @struct tvec_test *t@ = space for a test definition
- *
- * Returns: ---
- *
- * Use: Begin ad-hoc testing, i.e., without reading a file of
- * test-vector data.
- *
- * The structure at @t@ will be used to record information about
- * the tests underway, which would normally come from a static
- * test definition. The other functions in this section assume
- * that @tvec_adhoc@ has been called.
- */
-
-extern void tvec_adhoc(struct tvec_state */*tv*/, struct tvec_test */*t*/);
-
-/* --- @tvec_begingroup@, @TVEC_BEGINGROUP@ --- *
- *
- * Arguments: @struct tvec_state *tv@ = test-vector state
- * @const char *name@ = name for this test group
- * @const char *file@, @unsigned @lno@ = calling file and line
- *
- * Returns: ---
- *
- * Use: Begin an ad-hoc test group with the given name. The @file@
- * and @lno@ can be anything, but it's usually best if they
- * refer to the source code performing the test: the macro
- * @TVEC_BEGINGROUP@ does this automatically.
- */
-
-extern void tvec_begingroup(struct tvec_state */*tv*/, const char */*name*/,
- const char */*file*/, unsigned /*lno*/);
-#define TVEC_BEGINGROUP(tv, name) \
- do tvec_begingroup(tv, name, __FILE__, __LINE__); while (0)
-
-/* --- @tvec_endgroup@ --- *
- *
- * Arguments: @struct tvec_state *tv@ = test-vector state
- *
- * Returns: ---
- *
- * Use: End an ad-hoc test group. The statistics are updated and the
- * outcome is reported to the output formatter.
+ * Use: End an ad-hoc test group. The statistics are updated and the
+ * outcome is reported to the output formatter.
*/
extern void tvec_endgroup(struct tvec_state */*tv*/);
const char */*file*/, unsigned /*lno*/,
const char */*expr*/);
-/*----- Output formatting -------------------------------------------------*/
+/*----- Benchmarking ------------------------------------------------------*/
-struct tvec_output {
- /* An output formatter. */
- const struct tvec_outops *ops; /* pointer to operations */
+struct tvec_bench {
+ struct tvec_env _env; /* benchmarking is an environment */
+ struct bench_state **bst; /* benchmark state anchor or null */
+ unsigned long niter; /* iterations done per unit */
+ int riter, rbuf; /* iterations and buffer registers */
+ const struct tvec_env *env; /* subordinate environment */
};
+#define TVEC_BENCHENV \
+ { sizeof(struct tvec_benchctx), \
+ tvec_benchsetup, \
+ tvec_benchset, \
+ tvec_benchbefore, \
+ tvec_benchrun, \
+ tvec_benchafter, \
+ tvec_benchteardown }
+#define TVEC_BENCHINIT TVEC_BENCHENV, &tvec_benchstate
-/* Benchmarking details. */
-enum {
- TVBU_OP, /* counting operations of some kind */
- TVBU_BYTE /* counting bytes (@RBUF >= 0@) */
+struct tvec_benchctx {
+ const struct tvec_bench *b;
+ struct bench_state *bst;
+ double dflt_target;
+ void *subctx;
};
-struct bench_timing; /* forward declaration */
-struct tvec_outops {
- /* Output operations. */
+extern struct bench_state *tvec_benchstate;
- void (*bsession)(struct tvec_output */*o*/, struct tvec_state */*tv*/);
- /* Begin a test session. The output driver will probably want to
- * save @tv@, because this isn't provided to any other methods.
- */
+/* --- @tvec_benchsetup@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @const struct tvec_env *env@ = environment description
+ * @void *pctx@ = parent context (ignored)
+ * @void *ctx@ = context pointer to initialize
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Initialize a benchmarking environment context.
+ *
+ * The environment description must really be a @struct
+ * tvec_bench@. If the @bst@ slot is null, then a temporary
+ * benchmark state is allocated for the current test group and
+ * released at the end. Otherwise, it must be the address of a
+ * pointer to a benchmark state: if the pointer is null, then a
+ * fresh state is allocated and initialized and the pointer is
+ * updated; otherwise, the pointer is assumed to refer to an
+ * existing valid benchmark state.
+ */
- int (*esession)(struct tvec_output */*o*/);
- /* End a session, and return the suggested exit code. */
+extern int tvec_benchsetup(struct tvec_state */*tv*/,
+ const struct tvec_env */*env*/,
+ void */*pctx*/, void */*ctx*/);
- void (*bgroup)(struct tvec_output */*o*/);
- /* Begin a test group. The test group description is @tv->test@. */
+/* --- @tvec_benchset@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @const char *var@ = variable name to set
+ * @const struct tvec_env *env@ = environment description
+ * @void *ctx@ = context pointer
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Set a special variable. The following special variables are
+ * supported.
+ *
+ * * %|@target|% is the (approximate) number of seconds to run
+ * the benchmark.
+ *
+ * Unrecognized variables are passed to the subordinate
+ * environment, if there is one.
+ */
- void (*skipgroup)(struct tvec_output */*o*/,
- const char */*excuse*/, va_list */*ap*/);
- /* The group is being skipped; @excuse@ may be null or a format
- * string explaining why. The @egroup@ method will not be called
- * separately.
- */
+extern int tvec_benchset(struct tvec_state */*tv*/, const char */*var*/,
+ const struct tvec_env */*env*/, void */*ctx*/);
- void (*egroup)(struct tvec_output */*o*/);
- /* End a test group. At least one test was attempted or @skipgroup@
- * would have been called instead. If @tv->curr[TVOUT_LOSE]@ is
- * nonzero then the test group as a whole failed; otherwise it
- * passed.
- */
+/* --- @tvec_benchbefore@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @void *ctx@ = context pointer
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Invoke the subordinate environment's @before@ function to
+ * prepare for the benchmark.
+ */
- void (*btest)(struct tvec_output */*o*/);
- /* Begin a test case. */
+extern int tvec_benchbefore(struct tvec_state */*tv*/, void */*ctx*/);
- void (*skip)(struct tvec_output */*o*/,
- const char */*excuse*/, va_list */*ap*/);
- /* The test case is being skipped; @excuse@ may be null or a format
- * string explaining why. The @etest@ function will still be called
- * (so this works differently from @skipgroup@ and @egroup@). A test
- * case can be skipped at most once, and, if skipped, it cannot fail.
- */
+/* --- @tvec_benchrun@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @tvec_testfn *fn@ = test function to run
+ * @void *ctx@ = context pointer for the test function
+ *
+ * Returns: ---
+ *
+ * Use: Measures and reports the performance of a test function.
+ *
+ *
+ */
- void (*fail)(struct tvec_output */*o*/,
- const char */*detail*/, va_list */*ap*/);
- /* The test case failed.
- *
- * The output driver should preferably report the filename (@infile@)
- * and line number (@test_lno@, not @lno@) for the failing test.
- *
- * The @detail@ may be null or a format string describing detail
- * about how the failing test was run which can't be determined from
- * the registers; a @detail@ is usually provided when (and only when)
- * the test environment potentially invokes the test function more
- * than once.
- *
- * A single test case can fail multiple times!
- */
+extern void tvec_benchrun(struct tvec_state */*tv*/,
+ tvec_testfn */*fn*/, void */*ctx*/);
- void (*dumpreg)(struct tvec_output */*o*/,
- unsigned /*disp*/, const union tvec_regval */*rv*/,
- const struct tvec_regdef */*rd*/);
- /* Dump a register. The `disposition' @disp@ explains what condition
- * the register is in; see @tvec_dumpreg@ and the @TVRD_...@ codes.
- * The register value is at @rv@, and its definition, including its
- * type, at @rd@. Note that this function may be called on virtual
- * registers which aren't in either of the register vectors or
- * mentioned by the test description.
- */
+/* --- @tvec_benchafter@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @void *ctx@ = context pointer
+ *
+ * Returns: ---
+ *
+ * Use: Invoke the subordinate environment's @after@ function to
+ * clean up after the benchmark.
+ */
- void (*etest)(struct tvec_output */*o*/, unsigned /*outcome*/);
- /* The test case concluded with the given @outcome@ (one of the
- * @TVOUT_...@ codes.
- */
+extern void tvec_benchafter(struct tvec_state */*tv*/, void */*ctx*/);
- void (*bbench)(struct tvec_output */*o*/,
- const char */*ident*/, unsigned /*unit*/);
- /* Begin a benchmark. The @ident@ is a formatted string identifying
- * the benchmark based on the values of the input registered marked
- * @TVRF_ID@; the output driver is free to use this or come up with
- * its own way to identify the test, e.g., by inspecting the register
- * values for itself. The @unit@ is one of the @TVBU_...@ constants
- * explaining what sort of thing is being measured.
- */
+/* --- @tvec_benchteardown@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @void *ctx@ = context pointer
+ *
+ * Returns: ---
+ *
+ * Use: Tear down the benchmark environment.
+ */
- void (*ebench)(struct tvec_output */*o*/,
- const char */*ident*/, unsigned /*unit*/,
- const struct bench_timing */*tm*/);
- /* End a benchmark. The @ident@ and @unit@ arguments are as for
- * @bbench@. If @tm@ is zero then the measurement failed; otherwise
- * @tm->n@ counts total number of things (operations or bytes, as
- * indicated by @unit@) processed in the indicated time.
- */
+extern void tvec_benchteardown(struct tvec_state */*tv*/, void */*ctx*/);
- void (*error)(struct tvec_output */*o*/,
- const char */*msg*/, va_list */*ap*/);
- /* Report an error. The driver should ideally report the filename
- * (@infile@) and line number (@lno@) prompting the error.
- */
+/* --- @tvec_benchreport@ --- *
+ *
+ * Arguments: @const struct gprintf_ops *gops@ = print operations
+ * @void *go@ = print destination
+ * @unsigned unit@ = the unit being measured (~TVBU_...@)
+ * @const struct bench_timing *tm@ = the benchmark result
+ *
+ * Returns: ---
+ *
+ * Use: Formats a report about the benchmark performance. This
+ * function is intended to be called on by an output
+ * @ebench@ function.
+ */
- void (*notice)(struct tvec_output */*o*/,
- const char */*msg*/, va_list */*ap*/);
- /* Report a miscellaneous message. The driver should ideally report
- * the filename (@infile@) and line number (@lno@) prompting the
- * error.
- */
+extern void tvec_benchreport
+ (const struct gprintf_ops */*gops*/, void */*go*/,
+ unsigned /*unit*/, const struct bench_timing */*tm*/);
- void (*destroy)(struct tvec_output */*o*/);
- /* Release any resources acquired by the driver. */
-};
+/*----- Output functions --------------------------------------------------*/
/* --- @tvec_error@, @tvec_error_v@ --- *
*
extern struct tvec_output *tvec_dfltout(FILE */*fp*/);
-/*----- Register types ----------------------------------------------------*/
+/*------ Serialization utilities ------------------------------------------*/
-struct tvec_regty {
- /* A register type. */
+/* --- @tvec_serialize@ --- *
+ *
+ * Arguments: @const struct tvec_reg *rv@ = vector of registers
+ * @buf *b@ = buffer to write on
+ * @const struct tvec_regdef *regs@ = vector of register
+ * descriptions, terminated by an entry with a null
+ * @name@ slot
+ * @unsigned nr@ = number of entries in the @rv@ vector
+ * @size_t regsz@ = true size of each element of @rv@
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Serialize a collection of register values.
+ *
+ * The `candidate register definitions' are those entries @r@ in
+ * the @regs@ vector whose index @r.i@ is strictly less than
+ * @nr@. The `selected register definitions' are those
+ * candidate register definitions @r@ for which the indicated
+ * register @rv[r.i]@ has the @TVRF_LIVE@ flag set. The
+ * serialized output begins with a header bitmap: if there are
+ * %$n$% candidate register definitions then the header bitmap
+ * consists of %$\lceil n/8 \rceil$% bytes. Bits are ordered
+ * starting from the least significant bit of the first byte,
+ * end ending at the most significant bit of the final byte.
+ * The bit corresponding to a candidate register definition is
+ * set if and only if that register defintion is selected. The
+ * header bitmap is then followed by the serializations of the
+ * selected registers -- i.e., for each selected register
+ * definition @r@, the serialized value of register @rv[r.i]@ --
+ * simply concatenated together, with no padding or alignment.
+ *
+ * The serialized output is written to the buffer @b@. Failure
+ * can be caused by running out of buffer space, or a failing
+ * type handler.
+ */
- void (*init)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/);
- /* Initialize the value in @*rv@. This will be called before any
- * other function acting on the value, including @release@.
- */
+extern int tvec_serialize(const struct tvec_reg */*rv*/, buf */*b*/,
+ const struct tvec_regdef */*regs*/,
+ unsigned /*nr*/, size_t /*regsz*/);
- void (*release)(union tvec_regval */*rv*/,
- const struct tvec_regdef */*rd*/);
- /* Release any resources associated with the value in @*rv@. */
+/* --- @tvec_deserialize@ --- *
+ *
+ * Arguments: @struct tvec_reg *rv@ = vector of registers
+ * @buf *b@ = buffer to write on
+ * @const struct tvec_regdef *regs@ = vector of register
+ * descriptions, terminated by an entry with a null
+ * @name@ slot
+ * @unsigned nr@ = number of entries in the @rv@ vector
+ * @size_t regsz@ = true size of each element of @rv@
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: Deserialize a collection of register values.
+ *
+ * The size of the register vector @nr@ and the register
+ * definitions @regs@ must match those used when producing the
+ * serialization. For each serialized register value,
+ * deserialize and store the value into the appropriate register
+ * slot, and set the @TVRF_LIVE@ flag on the register. See
+ * @tvec_serialize@ for a description of the format.
+ *
+ * On successful completion, store the address of the first byte
+ * after the serialized data in @*end_out@ if @end_out@ is not
+ * null. Failure results only from a failing register type
+ * handler.
+ */
- int (*eq)(const union tvec_regval */*rv0*/,
- const union tvec_regval */*rv1*/,
- const struct tvec_regdef */*rd*/);
- /* Return nonzero if @*rv0@ and @*rv1@ are equal values.
- * Asymmetric criteria are permitted: @tvec_checkregs@ calls @eq@
- * with the input register as @rv0@ and the output as @rv1@.
- */
+extern int tvec_deserialize(struct tvec_reg */*rv*/, buf */*b*/,
+ const struct tvec_regdef */*regs*/,
+ unsigned /*nr*/, size_t /*regsz*/);
- int (*tobuf)(buf */*b*/, const union tvec_regval */*rv*/,
- const struct tvec_regdef */*rd*/);
- /* Serialize the value @*rv@, writing the result to @b@. Return
- * zero on success, or @-1@ on error.
- */
+/*----- Input utilities ---------------------------------------------------*/
+
+/* These are provided by the core for the benefit of type @parse@ methods,
+ * and test-environment @set@ functions, which get to read from the test
+ * input file. The latter are usually best implemented by calling on the
+ * former.
+ *
+ * The two main rules are as follows.
+ *
+ * * Leave the file position at the beginning of the line following
+ * whatever it was that you read.
+ *
+ * * When you read and consume a newline (which you do at least once, by
+ * the previous rule), then increment @tv->lno@ to keep track of the
+ * current line number.
+ */
+
+/* --- @tvec_skipspc@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns: ---
+ *
+ * Use: Advance over any whitespace characters other than newlines.
+ * This will stop at `;', end-of-file, or any other kind of
+ * non-whitespace; and it won't consume a newline.
+ */
+
+extern void tvec_skipspc(struct tvec_state */*tv*/);
+
+/* --- @tvec_syntax@, @tvec_syntax_v@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @int ch@ = the character found (in @fgetc@ format)
+ * @const char *expect@, @va_list ap@ = what was expected
+ *
+ * Returns: @-1@
+ *
+ * Use: Report a syntax error quoting @ch@ and @expect@. If @ch@ is
+ * a newline, then back up so that it can be read again (e.g.,
+ * by @tvec_flushtoeol@ or @tvec_nexttoken@, which will also
+ * advance the line number).
+ */
+
+extern int PRINTF_LIKE(3, 4)
+ tvec_syntax(struct tvec_state */*tv*/, int /*ch*/,
+ const char */*expect*/, ...);
+extern int tvec_syntax_v(struct tvec_state */*tv*/, int /*ch*/,
+ const char */*expect*/, va_list */*ap*/);
+
+/* --- @tvec_flushtoeol@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @unsigned f@ = flags (@TVFF_...@)
+ *
+ * Returns: Zero on success, @-1@ on error.
+ *
+ * Use: Advance to the start of the next line, consuming the
+ * preceding newline.
+ *
+ * A syntax error is reported if no newline character is found,
+ * i.e., the file ends in mid-line. A syntax error is also
+ * reported if material other than whitespace or a comment is
+ * found before the end of the line end, and @TVFF_ALLOWANY@ is
+ * not set in @f@. The line number count is updated
+ * appropriately.
+ */
+
+#define TVFF_ALLOWANY 1u
+extern int tvec_flushtoeol(struct tvec_state */*tv*/, unsigned /*f*/);
+
+/* --- @tvec_nexttoken@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns: Zero if there is a next token which can be read; @-1@ if no
+ * token is available.
+ *
+ * Use: Advance to the next whitespace-separated token, which may be
+ * on the next line.
+ *
+ * Tokens are separated by non-newline whitespace, comments, and
+ * newlines followed by whitespace; a newline /not/ followed by
+ * whitespace instead begins the next assignment, and two
+ * newlines separated only by whitespace terminate the data for
+ * a test.
+ *
+ * If this function returns zero, then the next character in the
+ * file begins a suitable token which can be read and
+ * processed. If it returns @-1@ then there is no such token,
+ * and the file position is left correctly. The line number
+ * count is updated appropriately.
+ */
- int (*frombuf)(buf */*b*/, union tvec_regval */*rv*/,
- const struct tvec_regdef */*rd*/);
- /* Deserialize a value from @b@, storing it in @*rv@. Return zero on
- * success, or @-1@ on error.
- */
+extern int tvec_nexttoken(struct tvec_state */*tv*/);
- int (*parse)(union tvec_regval */*rv*/, const struct tvec_regdef */*rd*/,
- struct tvec_state */*tv*/);
- /* Parse a value from @tv->fp@, storing it in @*rv@. Return zero on
- * success, or @-1@ on error, having reported one or more errors via
- * @tvec_error@ or @tvec_syntax@. A successful return should leave
- * the input position at the start of the next line; the caller will
- * flush the remainder of the line itself.
- */
+/* --- @tvec_readword@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @dstr *d@ = string to append the word to
+ * @const char *delims@ = additional delimiters to stop at
+ * @const char *expect@, @va_list ap@ = what was expected
+ *
+ * Returns: Zero on success, @-1@ on failure.
+ *
+ * Use: A `word' consists of characters other than whitespace, null
+ * characters, and other than those listed in @delims@;
+ * furthermore, a word does not begin with a `;'. (If you want
+ * reading to stop at comments not preceded by whitespace, then
+ * include `;' in @delims@. This is a common behaviour.)
+ *
+ * If there is no word beginning at the current file position,
+ * then return @-1@; furthermore, if @expect@ is not null, then
+ * report an appropriate error via @tvec_syntax@.
+ *
+ * Otherwise, the word is accumulated in @d@ and zero is
+ * returned; if @d@ was not empty at the start of the call, the
+ * newly read word is separated from the existing material by a
+ * single space character. Since null bytes are never valid
+ * word constituents, a null terminator is written to @d@, and
+ * it is safe to treat the string in @d@ as being null-
+ * terminated.
+ */
- void (*dump)(const union tvec_regval */*rv*/,
- const struct tvec_regdef */*rd*/,
- unsigned /*style*/,
- const struct gprintf_ops */*gops*/, void */*go*/);
-#define TVSF_COMPACT 1u
- /* Write a human-readable representation of the value @*rv@ using
- * @gprintf@ on @gops@ and @go@. The @style@ is a collection of
- * flags: if @TVSF_COMPACT@ is set, then output should be minimal,
- * and must fit on a single line; otherwise, output may consist of
- * multiple lines and may contain redundant information if that is
- * likely to be useful to a human reader.
- */
-};
+extern int PRINTF_LIKE(4, 5)
+ tvec_readword(struct tvec_state */*tv*/, dstr */*d*/,
+ const char */*delims*/, const char */*expect*/, ...);
+extern int tvec_readword_v(struct tvec_state */*tv*/, dstr */*d*/,
+ const char */*delims*/, const char */*expect*/,
+ va_list */*ap*/);
-/*----- Integer types: signed and unsigned ------------------------------- */
+/*----- Integer types: signed and unsigned --------------------------------*/
/* Integers may be input in decimal, hex, binary, or octal, following
* approximately usual conventions.
tvrange_uchar, tvrange_ushort, tvrange_uint, tvrange_ulong, tvrange_size,
tvrange_byte, tvrange_u16, tvrange_u32;
-/* --- tvec_claimeq_int@, @TVEC_CLAIMEQ_INT@ --- *
+/* --- @tvec_claimeq_int@, @TVEC_CLAIMEQ_INT@ --- *
*
* Arguments: @struct tvec_state *tv@ = test-vector state
* @long i0, i1@ = two signed integers
/*----- Enumerated types --------------------------------------------------*/
-/* There is a distinct enumerated type for each of the branches of
+/* An enumeration describes a set of values of some underlying type, each of
+ * which has a symbolic name. Values outside of the defined set can occur --
+ * on output, because of bugs in the tested code, or on input to test
+ * handling of unexpected values.
+ *
+ * There is a distinct enumerated type for each of the branches of
* @tvec_misc@. In the following, we write @t@ for the type code, which is
* @i@ for signed integer, @u@ for unsigned integer, @f@ for floating-point,
* and @p@ for pointer.
+ *
+ * On input, an enumerated value may be given by name or as a literal value.
+ * For enumerations based on numeric types, the literal values can be written
+ * in the same syntax as the underlying values. For enumerations based on
+ * pointers, the only permitted literal is `#nil', which denotes a null
+ * pointer. On output, names are preferred (with the underlying value given
+ * in a comment).
*/
#define DEFENUMTY(tag, ty, slot) \
TVEC_MISCSLOTS(DEFASSOC)
#undef DEFASSOC
+#define TVEC_ENDENUM { 0, 0 }
+
/* Information about an enumerated type. */
#define DEFINFO(tag, ty, slot) \
struct tvec_##slot##enuminfo { \
/* --- @tvec_claimeq_tenum@, @TVEC_CLAIMEQ_TENUM@ --- *
*
* Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const struct tvec_typeenuminfo *ei@ = enumeration type info
* @ty t0, t1@ = two values
* @const char *file@, @unsigned @lno@ = calling file and line
* @const char *expr@ = the expression to quote on failure
* @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.
+ * mismatched values are dumped: @t0@ is printed as the output
+ * value and @t1@ is printed as the input reference.
*
* The @TVEC_CLAIM_TENUM@ macro is similar, only it (a)
* identifies the file and line number of the call site
* automatically, and (b) implicitly quotes the source text of
- * the @u0@ and @u1@ arguments in the failure message.
+ * the @t0@ and @t1@ arguments in the failure message.
*/
#define DECLCLAIM(tag, ty, slot) \
(tvec_claimeq_penum(tv, ei, p0, p1, \
__FILE__, __LINE__, #p0 " /= " #p1))
-/*----- Flags type -------------------------------------------------------*/
+/*----- Flags type --------------------------------------------------------*/
+
+/* A flags value packs a number of fields into a single nonnegative integer.
+ * Symbolic names are associated with the possible values of the various
+ * fields; more precisely, each name is associated with a value and a
+ * covering bitmask.
+ *
+ * The input syntax is a sequence of items separated by `|' signs. Each item
+ * may be the symbolic name of a field value, or a literal unsigned integer.
+ * The masks associated with the given symbolic names must be disjoint. The
+ * resulting numerical value is simply the bitwise OR of the given values.
+ *
+ * On output, the table of symbolic names and their associated values and
+ * masks is repeatedly scanned, in order, to find disjoint matches -- i.e.,
+ * entries whose value matches the target value in the bit positions
+ * indicated by the mask, and whose mask doesn't overlap with any previously
+ * found matches; the names are then output, separated by `|'. Any remaining
+ * nonzero bits not covered by any of the matching masks are output as a
+ * single literal integer, in hex.
+ */
extern const struct tvec_regty tvty_flags;
/* Definition of a single flag or bitfield value.
*
* Each named setting comes with a value @v@ and a mask @m@; the mask
- * should cover all of the value bits, i.e., @(v&~m) == 0@. On input,
- * exactly
+ * should cover all of the value bits, i.e., @(v&~m) == 0@.
*/
const char *tag; /* name */
unsigned long m, v; /* mask and value */
};
+#define TVEC_ENDFLAGS { 0, 0, 0 }
+
struct tvec_flaginfo {
- const char *name;
- const struct tvec_flag *fv;
- const struct tvec_urange *range;
+ /* Information about a flags type. */
+
+ const char *name; /* type name for diagnostics */
+ const struct tvec_flag *fv; /* name/mask/value mappings */
+ const struct tvec_urange *range; /* permitted range for literals */
};
+/* --- @tvec_claimeq_flags@, @TVEC_CLAIMEQ_FLAGS@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const struct tvec_flaginfo *fi@ = flags type info
+ * @unsigned long f0, f1@ = two values
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @f0@ and @f1@ are equal, otherwise zero.
+ *
+ * Use: Check that values of @f0@ and @f1@ 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: @f0@ is printed as the output
+ * value and @f1@ is printed as the input reference.
+ *
+ * The @TVEC_CLAIM_FLAGS@ macro is similar, only it (a)
+ * identifies the file and line number of the call site
+ * automatically, and (b) implicitly quotes the source text of
+ * the @f0@ and @f1@ arguments in the failure message.
+ */
+
extern int tvec_claimeq_flags(struct tvec_state */*tv*/,
const struct tvec_flaginfo */*fi*/,
unsigned long /*f0*/, unsigned long /*f1*/,
(tvec_claimeq_flags(tv, fi, f0, f1, \
__FILE__, __LINE__, #f0 " /= " #f1))
+/*----- Character type ----------------------------------------------------*/
+
+/* A character value holds a character, as read by @fgetc@. The special
+ * @EOF@ value can also be represented.
+ *
+ * On input, a character value can be given by name, with a leading `%|#|%';
+ * or a character or `%|\|%'-escape sequence, optionally in single quotes.
+ *
+ * The following escape sequences and character names are recognized.
+ *
+ * * `%|#eof|%' is the special end-of-file marker.
+ *
+ * * `%|#nul|%' is the NUL character, sometimes used to terminate strings.
+ *
+ * * `%|bell|%', `%|bel|%', `%|ding|%', or `%|\a|%' is the BEL character
+ * used to ring the terminal bell (or do some other thing to attract the
+ * user's attention).
+ *
+ * * %|#backspace|%, %|#bs|%, or %|\b|% is the backspace character, used to
+ * move the cursor backwords by one cell.
+ *
+ * * %|#escape|% %|#esc|%, or%|\e|% is the escape character, used to
+ * introduce special terminal commands.
+ *
+ * * %|#formfeed|%, %|#ff|%, or %|\f|% is the formfeed character, used to
+ * separate pages of text.
+ *
+ * * %|#newline|%, %|#linefeed|%, %|#lf|%, %|#nl|%, or %|\n|% is the
+ * newline character, used to terminate lines of text or advance the
+ * cursor to the next line (perhaps without returning it to the start of
+ * the line).
+ *
+ * * %|#return|%, %|#carriage-return|%, %|#cr|%, or %|\r|% is the
+ * carriage-return character, used to return the cursor to the start of
+ * the line.
+ *
+ * * %|#tab|%, %|#horizontal-tab|%, %|#ht|%, or %|\t|% is the tab
+ * character, used to advance the cursor to the next tab stop on the
+ * current line.
+ *
+ * * %|#vertical-tab|%, %|#vt|%, %|\v|% is the vertical tab character.
+ *
+ * * %|#space|%, %|#spc|% is the space character.
+ *
+ * * %|#delete|%, %|#del|% is the delete character, used to erase the most
+ * recent character.
+ *
+ * * %|\'|% is the single-quote character.
+ *
+ * * %|\\|% is the backslash character.
+ *
+ * * %|\"|% is the double-quote character.
+ *
+ * * %|\NNN|% or %|\{NNN}|% is the character with code NNN in octal. The
+ * NNN may be up to three digits long.
+ *
+ * * %|\xNN|% or %|\x{NN}|% is the character with code NNN in hexadecimal.
+ */
+
extern const struct tvec_regty tvty_char;
+
+/* --- @tvec_claimeq_char@, @TVEC_CLAIMEQ_CHAR@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @int ch0, ch1@ = two character codes
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if @ch0@ and @ch1@ are equal, otherwise zero.
+ *
+ * Use: Check that values of @ch0@ and @ch1@ 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: @ch0@ is printed as the output
+ * value and @ch1@ is printed as the input reference.
+ *
+ * The @TVEC_CLAIM_CHAR@ macro is similar, only it (a)
+ * identifies the file and line number of the call site
+ * automatically, and (b) implicitly quotes the source text of
+ * the @ch0@ and @ch1@ arguments in the failure message.
+ */
+
extern int tvec_claimeq_char(struct tvec_state */*tv*/,
int /*ch0*/, int /*ch1*/,
const char */*file*/, unsigned /*lno*/,
#define TVEC_CLAIMEQ_CHAR(tv, c0, c1) \
(tvec_claimeq_char(tv, c0, c1, __FILE__, __LINE__, #c0 " /= " #c1))
+/*----- Text and binary string types --------------------------------------*/
+
+/* A string is a sequence of octets. Text and binary strings differ
+ * primarily in presentation: text strings are shown as raw characters where
+ * possible; binary strings are shown as hex dumps with an auxiliary text
+ * display.
+ *
+ * The input format for both kinds of strings is basically the same: a
+ * `compound string', consisting of
+ *
+ * * single-quoted strings, which are interpreted entirely literally, but
+ * can't contain single quotes or newlines;
+ *
+ * * double-quoted strings, in which `%|\|%'-escapes are interpreted as for
+ * characters;
+ *
+ * * character names, marked by an initial `%|#|%' sign;
+ *
+ * * special tokens marked by an initial `%|!|%' sign; or
+ *
+ * * barewords interpreted according to the current coding scheme.
+ *
+ * The special tokens are
+ *
+ * * `%|!bare|%', which causes subsequent sequences of barewords to be
+ * treated as plain text;
+ *
+ * * `%|!hex|%', `%|!base32|%', `%|!base64|%', which cause subsequent
+ * barewords to be decoded in the requested manner.
+ *
+ * * `%|!repeat|% %$n$% %|{|% %%\textit{string}%% %|}|%', which includes
+ * %$n$% copies of the (compound) string.
+ *
+ * Either kind of string can contain internal nul characters. A trailing nul
+ * is appended -- beyond the stated input length -- to input strings as a
+ * convenience to test functions. Test functions may include such a nul
+ * character on output but this is not checked by the equality test.
+ *
+ * A @struct tvec_urange@ may be supplied as an argument: the length of the
+ * string (in bytes) will be checked against the permitted range.
+ */
+
extern const struct tvec_regty tvty_string, tvty_bytes;
+/* --- @tvec_claimeq_string@, @TVEC_CLAIMEQ_STRING@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *p0@, @size_t sz0@ = first string with length
+ * @const char *p1@, @size_t sz1@ = second string with length
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if the strings at @p0@ and @p1@ are equal, otherwise
+ * zero.
+ *
+ * Use: Check that strings at @p0@ and @p1@ 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: @p0@ is printed as the output
+ * value and @p1@ is printed as the input reference.
+ *
+ * The @TVEC_CLAIM_STRING@ macro is similar, only it (a)
+ * identifies the file and line number of the call site
+ * automatically, and (b) implicitly quotes the source text of
+ * the @ch0@ and @ch1@ arguments in the failure message.
+ */
+
extern 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*/);
+#define TVEC_CLAIMEQ_STRING(tv, p0, sz0, p1, sz1) \
+ (tvec_claimeq_string(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \
+ #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]"))
+
+/* --- @tvec_claimeq_strz@, @TVEC_CLAIMEQ_STRZ@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const char *p0, *p1@ = two strings to compare
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if the strings at @p0@ and @p1@ are equal, otherwise
+ * zero.
+ *
+ * Use: Check that strings at @p0@ and @p1@ are equal, as for
+ * @tvec_claimeq_string@, except that the strings are assumed
+ * null-terminated, so their lengths don't need to be supplied
+ * explicitly. The macro is similarly like
+ * @TVEC_CLAIMEQ_STRING@.
+ */
+
extern int tvec_claimeq_strz(struct tvec_state */*tv*/,
const char */*p0*/, const char */*p1*/,
const char */*file*/, unsigned /*lno*/,
const char */*expr*/);
+#define TVEC_CLAIMEQ_STRZ(tv, p0, p1) \
+ (tvec_claimeq_strz(tv, p0, p1, __FILE__, __LINE__, #p0 " /= " #p1))
+
+/* --- @tvec_claimeq_bytes@, @TVEC_CLAIMEQ_BYTES@ --- *
+ *
+ * Arguments: @struct tvec_state *tv@ = test-vector state
+ * @const void *p0@, @size_t sz0@ = first string with length
+ * @const void *p1@, @size_t sz1@ = second string with length
+ * @const char *file@, @unsigned @lno@ = calling file and line
+ * @const char *expr@ = the expression to quote on failure
+ *
+ * Returns: Nonzero if the strings at @p0@ and @p1@ are equal, otherwise
+ * zero.
+ *
+ * Use: Check that binary strings at @p0@ and @p1@ 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: @p0@ is printed as the output
+ * value and @p1@ is printed as the input reference.
+ *
+ * The @TVEC_CLAIM_STRING@ macro is similar, only it (a)
+ * identifies the file and line number of the call site
+ * automatically, and (b) implicitly quotes the source text of
+ * the @ch0@ and @ch1@ arguments in the failure message.
+ */
+
extern 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*/);
-#define TVEC_CLAIMEQ_STRING(tv, p0, sz0, p1, sz1) \
- (tvec_claimeq_string(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \
- #p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]"))
-#define TVEC_CLAIMEQ_STRZ(tv, p0, p1) \
- (tvec_claimeq_strz(tv, p0, p1, __FILE__, __LINE__, #p0 " /= " #p1))
#define TVEC_CLAIMEQ_BYTES(tv, p0, sz0, p1, sz1) \
(tvec_claimeq(tv, p0, sz0, p1, sz1, __FILE__, __LINE__, \
#p0 "[" #sz0 "] /= " #p1 "[" #sz1 "]"))
-extern const struct tvec_regty tvty_buffer;
+/* --- @tvec_allocstring@, @tvec_allocbytes@ --- *
+ *
+ * Arguments: @union tvec_regval *rv@ = register value
+ * @size_t sz@ = required size
+ *
+ * Returns: ---
+ *
+ * Use: Allocated space in a text or binary string register. If the
+ * current register size is sufficient, its buffer is left
+ * alone; otherwise, the old buffer, if any, is freed and a
+ * fresh buffer allocated. These functions are not intended to
+ * be used to adjust a buffer repeatedly, e.g., while building
+ * output incrementally: (a) they will perform badly, and (b)
+ * the old buffer contents are simply discarded if reallocation
+ * is necessary. Instead, use a @dbuf@ or @dstr@.
+ *
+ * The @tvec_allocstring@ function sneakily allocates an extra
+ * byte for a terminating zero. The @tvec_allocbytes@ function
+ * doesn't do this.
+ */
extern void tvec_allocstring(union tvec_regval */*rv*/, size_t /*sz*/);
extern void tvec_allocbytes(union tvec_regval */*rv*/, size_t /*sz*/);
-/*----- Benchmarking ------------------------------------------------------*/
-
-struct tvec_bench {
- struct tvec_env _env; /* benchmarking is an environment */
- struct bench_state **bst; /* benchmark state anchor or null */
- unsigned long niter; /* iterations done per unit */
- int riter, rbuf; /* iterations and buffer registers */
- const struct tvec_env *env; /* environment (per test, not grp) */
-};
-#define TVEC_BENCHENV \
- { sizeof(struct tvec_benchctx), \
- tvec_benchsetup, \
- tvec_benchset, \
- tvec_benchbefore, \
- tvec_benchrun, \
- tvec_benchafter, \
- tvec_benchteardown }
-#define TVEC_BENCHINIT TVEC_BENCHENV, &tvec_benchstate
-
-struct tvec_benchctx {
- const struct tvec_bench *b;
- struct bench_state *bst;
- double dflt_target;
- void *subctx;
-};
-
-extern struct bench_state *tvec_benchstate;
-
-extern int tvec_benchsetup(struct tvec_state */*tv*/,
- const struct tvec_env */*env*/,
- void */*pctx*/, void */*ctx*/);
-extern int tvec_benchset(struct tvec_state */*tv*/, const char */*var*/,
- const struct tvec_env */*env*/, void */*ctx*/);
-extern int tvec_benchbefore(struct tvec_state */*tv*/, void */*ctx*/);
-extern void tvec_benchrun(struct tvec_state */*tv*/,
- tvec_testfn */*fn*/, void */*ctx*/);
-extern void tvec_benchafter(struct tvec_state */*tv*/, void */*ctx*/);
-extern void tvec_benchteardown(struct tvec_state */*tv*/, void */*ctx*/);
-
-extern void tvec_benchreport
- (const struct gprintf_ops */*gops*/, void */*go*/,
- unsigned /*unit*/, const struct bench_timing */*tm*/);
-
-/*----- Command-line interface --------------------------------------------*/
-
-extern const struct tvec_config tvec_adhocconfig;
-
-extern void tvec_parseargs(int /*argc*/, char */*argv*/[],
- struct tvec_state */*tv_out*/,
- int */*argpos_out*/,
- const struct tvec_config */*config*/);
-
-extern int tvec_readstdin(struct tvec_state */*tv*/);
-extern int tvec_readfile(struct tvec_state */*tv*/, const char */*file*/);
-extern int tvec_readdflt(struct tvec_state */*tv*/, const char */*file*/);
-extern int tvec_readarg(struct tvec_state */*tv*/, const char */*arg*/);
+/*----- Buffer type -------------------------------------------------------*/
-extern int tvec_readargs(int /*argc*/, char */*argv*/[],
- struct tvec_state */*tv*/,
- int */*argpos_inout*/, const char */*dflt*/);
+/* Buffer registers are primarily used for benchmarking. Only a buffer's
+ * size is significant: its contents are ignored on comparison and output,
+ * and unspecified on input.
+ *
+ * The input is simply the buffer size, as an integer, optionally suffixed
+ * with a unit `kB', `MB', `GB', `TB', `PB', `EB', `ZB', `YB' (with or
+ * without the `B') denoting a power of 1024. Units are used on output only
+ * when the size would be expressed exactly.
+ */
-extern int tvec_main(int /*argc*/, char */*argv*/[],
- const struct tvec_config */*config*/,
- const char */*dflt*/);
+extern const struct tvec_regty tvty_buffer;
/*----- That's all, folks -------------------------------------------------*/
libutils_la_SOURCES += linreg.c
##LIBMANS += linreg.3
+## Mathematics.
+pkginclude_HEADERS += maths.h
+##LIBMANS += maths.3
+
## String handling.
pkginclude_HEADERS += str.h
libutils_la_SOURCES += str.c
return (n);
}
+/*----- Utilities ---------------------------------------------------------*/
+
+/* --- @gprintf_memputf@ --- *
+ *
+ * Arguments: @char **buf_inout@ = address of output buffer pointer
+ * @size_t *sz_inout@ = address of buffer size
+ * @size_t maxsz@ = buffer size needed for this operation
+ * @const char *p@ = pointer to format string
+ * @va_list *ap@ = captured format-arguments tail
+ *
+ * Returns: The formatted length.
+ *
+ * Use: Generic utility for mostly implementing the @nputf@ output
+ * function, if you don't have a better option.
+ *
+ * On entry, @*buf_inout@ should be null or a buffer pointer,
+ * with @*sz_inout@ either zero or the buffer's size,
+ * respectively. On exit, @*buf_input@ and @*sz_inout@ will be
+ * updated, if necessary, to describe a sufficiently large
+ * buffer, and the formatted string will have been written to
+ * the buffer.
+ *
+ * When the buffer is no longer required, free it using @xfree@.
+ */
+
+size_t gprintf_memputf(char **buf_inout, size_t *sz_inout,
+ size_t maxsz, const char *p, va_list ap)
+{
+ char *buf = *buf_inout;
+ size_t sz = *sz_inout;
+ int n;
+
+ if (sz <= maxsz) {
+ if (!sz) sz = 32;
+ while (sz <= maxsz) sz *= 2;
+ if (buf) xfree(buf);
+ buf = xmalloc(sz); *buf_inout = buf; *sz_inout = sz;
+ }
+
+#ifdef HAVE_SNPRINTF
+ n = vsnprintf(buf, maxsz + 1, p, ap);
+#else
+ n = vsprintf(buf, p, ap);
+#endif
+ assert(0 <= n && n <= maxsz);
+ return (n);
+}
+
/*----- Standard printers -------------------------------------------------*/
static int file_putch(void *out, int ch)
gprintf(const struct gprintf_ops */*ops*/, void */*out*/,
const char */*p*/, ...);
+/* --- @gprintf_memputf@ --- *
+ *
+ * Arguments: @char **buf_inout@ = address of output buffer pointer
+ * @size_t *sz_inout@ = address of buffer size
+ * @size_t maxsz@ = buffer size needed for this operation
+ * @const char *p@ = pointer to format string
+ * @va_list *ap@ = captured format-arguments tail
+ *
+ * Returns: The formatted length.
+ *
+ * Use: Generic utility for mostly implementing the @nputf@ output
+ * function, if you don't have a better option.
+ *
+ * On entry, @*buf_inout@ should be null or a buffer pointer,
+ * with @*sz_inout@ either zero or the buffer's size,
+ * respectively. On exit, @*buf_input@ and @*sz_inout@ will be
+ * updated, if necessary, to describe a sufficiently large
+ * buffer, and the formatted string will have been written to
+ * the buffer.
+ *
+ * When the buffer is no longer required, free it using @xfree@.
+ */
+
+extern size_t gprintf_memputf(char **/*buf_inout*/, size_t */*sz_inout*/,
+ size_t /*maxsz*/,
+ const char */*p*/, va_list /*ap*/);
+
/*----- That's all, folks -------------------------------------------------*/
#ifdef __cplusplus
--- /dev/null
+/* -*-c-*-
+ *
+ * Mathematical utilities
+ *
+ * (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.
+ */
+
+#ifndef MLIB_MATHS_H
+#define MLIB_MATHS_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <math.h>
+
+/*----- Macros provided ---------------------------------------------------*/
+
+/* --- @NANP@ --- *
+ *
+ * Arguments: @floatish x@ = a floating-point number (evaluated multiple
+ * times)
+ *
+ * Returns: Nonzero if @x@ is not-a-number.
+ */
+
+#ifdef isnan
+# define NANP(x) isnan(x)
+#else
+# define NANP(x) (!((x) == (x)))
+#endif
+
+/* --- @INFP@ --- *
+ *
+ * Arguments: @floatish x@ = a floating-point number (evaluated multiple
+ * times)
+ *
+ * Returns: Nonzero if @x@ is infinite.
+ */
+
+#ifdef isinf
+# define INFP(x) isinf(x)
+#else
+# define INFP(x) ((x) > LDBL_MAX || (x) < -LDBL_MAX)
+#endif
+
+/* --- @NEGP@ --- *
+ *
+ * Arguments: @floatish x@ = a floating-point number (evaluated multiple
+ * times)
+ *
+ * Returns: Nonzero if @x@ is negative. This won't give the right answer
+ * for negative zero unless the system provides explicit
+ * support.
+ */
+
+#ifdef signbit
+# define NEGP(x) signbit(x)
+#else
+# define NEGP(x) ((x) < 0)
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif