@@@ tvec setvar
[mLib] / test / tvec-types.c
index acb848c..e76d4a1 100644 (file)
@@ -525,6 +525,7 @@ static void format_floating(const struct gprintf_ops *gops, void *go,
 /* --- @parse_floating@ --- *
  *
  * Arguments:  @double *x_out@ = where to put the result
+ *             @const char *q_out@ = where to leave end pointer, or null
  *             @const char *p@ = string to parse
  *             @const struct tvec_floatinfo *fi@ = floating-point info
  *             @struct tvec_state *tv@ = test vector state
@@ -532,10 +533,12 @@ static void format_floating(const struct gprintf_ops *gops, void *go,
  * Returns:    Zero on success, @-1@ on error.
  *
  * Use:                Parse a floating-point number from a string.  Reports any
- *             necessary errors.
+ *             necessary errors.  If @q_out@ is not null then trailing
+ *             material is permitted and a pointer to it is left in
+ *             @*q_out@; this will be null if there is no trailing material.
  */
 
-static int parse_floating(double *x_out, const char *p,
+static int parse_floating(double *x_out, const char **q_out, const char *p,
                          const struct tvec_floatinfo *fi,
                          struct tvec_state *tv)
 {
@@ -544,6 +547,8 @@ static int parse_floating(double *x_out, const char *p,
   double x;
   int olderr, rc;
 
+  if (q_out) *q_out = 0;
+
   /* Check for special tokens. */
   if (STRCMP(p, ==, "#nan")) {
 #ifdef NAN
@@ -588,13 +593,12 @@ static int parse_floating(double *x_out, const char *p,
     /* Parse the number using the system parser. */
     olderr = errno; errno = 0;
     x = strtod(p, &q);
-    if (*q) {
-      tvec_syntax(tv, *q, "end-of-line");
-      rc = -1; goto end;
-    }
+    if (!*q) /* nothing to do */;
+    else if (q_out) *q_out = q;
+    else { tvec_syntax(tv, *q, "end-of-line"); rc = -1; goto end; }
     if (errno && (errno != ERANGE || (x > 0 ? -x : x) == HUGE_VAL)) {
-      tvec_error(tv, "invalid floating-point number `%s': %s",
-                p, strerror(errno));
+      tvec_error(tv, "invalid floating-point number `%.*s': %s",
+                (int)(q - p), p, strerror(errno));
       rc = -1; goto end;
     }
     errno = olderr;
@@ -1515,7 +1519,7 @@ int tvec_claimeq_uint(struct tvec_state *tv,
 
 /*----- Floating-point type -----------------------------------------------*/
 
-/* --- @float_int@ --- *
+/* --- @int_float@ --- *
  *
  * Arguments:  @union tvec_regval *rv@ = register value
  *             @const struct tvec_regdef *rd@ = register definition
@@ -1610,7 +1614,8 @@ static int parse_float(union tvec_regval *rv, const struct tvec_regdef *rd,
 
   if (tvec_readword(tv, &d, ";", "floating-point number"))
     { rc = -1; goto end; }
-  if (parse_floating(&rv->f, d.buf, rd->arg.p, tv)) { rc = -1; goto end; }
+  if (parse_floating(&rv->f, 0, d.buf, rd->arg.p, tv))
+    { rc = -1; goto end; }
   if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
   rc = 0;
 end:
@@ -1729,6 +1734,151 @@ int tvec_claimeq_float(struct tvec_state *tv,
                                file, lno, expr));
 }
 
+/*----- Durations ---------------------------------------------------------*/
+
+/* A duration is a floating-point number of seconds.  Initialization and
+ * teardown, equality comparison, and serialization are as for floating-point
+ * values.
+ */
+
+static const struct duration_unit {
+  const char *unit;
+  double scale;
+  unsigned f;
+#define DUF_PREFER 1u
+} duration_units[] = {
+  { "Ys",      1e+24,          0 },
+  { "Zs",      1e+21,          0 },
+  { "Es",      1e+18,          0 },
+  { "Ps",      1e+15,          0 },
+  { "Ts",      1e+12,          0 },
+  { "Gs",      1e+9,           0 },
+  { "Ms",      1e+6,           0 },
+  { "ks",      1e+3,           0 },
+  { "hs",      1e+2,           0 },
+  { "das",     1e+1,           0 },
+
+  { "yr",      31557600.0,     DUF_PREFER },
+  { "y",       31557600.0,     0 },
+  { "day",     86400.0,        DUF_PREFER },
+  { "dy",      86400.0,        0 },
+  { "d",       86400.0,        0 },
+  { "hr",      3600.0,         DUF_PREFER },
+  { "hour",    3600.0,         0 },
+  { "h",       3600.0,         0 },
+  { "min",     60.0,           DUF_PREFER },
+  { "m",       60.0,           0 },
+
+  { "s",       1.0,            DUF_PREFER },
+  { "sec",     1.0,            0 },
+
+  { "ds",      1e-1,           0 },
+  { "cs",      1e-2,           0 },
+  { "ms",      1e-3,           DUF_PREFER },
+  { "µs",     1e-6,           DUF_PREFER },
+  { "ns",      1e-9,           DUF_PREFER },
+  { "ps",      1e-12,          DUF_PREFER },
+  { "fs",      1e-15,          DUF_PREFER },
+  { "as",      1e-18,          DUF_PREFER },
+  { "zs",      1e-21,          DUF_PREFER },
+  { "ys",      1e-24,          DUF_PREFER },
+
+  { 0 }
+};
+
+/* --- @parse_duration@ --- *
+ *
+ * Arguments:  @union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @struct tvec_state *tv@ = test-vector state
+ *
+ * Returns:    Zero on success, %$-1$% on error.
+ *
+ * Use:                Parse a register value from an input file.
+ *
+ *             Duration values are finite nonnegative floating-point
+ *             numbers in @strtod@ syntax, optionally followed by a unit .
+ */
+
+static int parse_duration(union tvec_regval *rv,
+                         const struct tvec_regdef *rd,
+                         struct tvec_state *tv)
+{
+  const struct duration_unit *u;
+  const char *q;
+  dstr d = DSTR_INIT; size_t pos;
+  double t;
+  int rc;
+
+  if (tvec_readword(tv, &d, ";", "duration")) { rc = -1; goto end; }
+  if (parse_floating(&t, &q, d.buf,
+                    rd->arg.p ? rd->arg.p : &tvflt_nonneg, tv))
+    { rc = -1; goto end; }
+
+  if (!q) {
+    tvec_skipspc(tv); pos = d.len;
+    if (!tvec_readword(tv, &d, ";", 0)) q = d.buf + pos + 1;
+  }
+
+  if (q) {
+    for (u = duration_units; u->unit; u++)
+      if (STRCMP(q, ==, u->unit)) { t *= u->scale; goto found_unit; }
+    rc = tvec_syntax(tv, *q, "end-of-line"); goto end;
+  found_unit:;
+  }
+
+  if (tvec_flushtoeol(tv, 0)) { rc = -1; goto end; }
+  rv->f = t; rc = 0;
+end:
+  dstr_destroy(&d);
+  return (rc);
+}
+
+/* --- @dump_duration@ --- *
+ *
+ * Arguments:  @const union tvec_regval *rv@ = register value
+ *             @const struct tvec_regdef *rd@ = register definition
+ *             @unsigned style@ = output style (@TVSF_...@)
+ *             @const struct gprintf_ops *gops@, @void *gp@ = format output
+ *
+ * Returns:    ---
+ *
+ * Use:                Dump a register value to the format output.
+ *
+ *             Durations are dumped as a human-palatable scaled value with
+ *             unit, and, if compact style is not requested, as a raw number
+ *             of seconds at full precision as a comment.
+ */
+
+static void dump_duration(const union tvec_regval *rv,
+                         const struct tvec_regdef *rd,
+                         unsigned style,
+                         const struct gprintf_ops *gops, void *go)
+{
+  const struct duration_unit *u;
+  double t = rv->f;
+
+  if (!t) u = 0;
+  else {
+    for (u = duration_units; u->scale > t && u[1].unit; u++);
+    t /= u->scale;
+  }
+
+  gprintf(gops, go, "%.4g %s", t, u ? u->unit : "s");
+  if (!(style&TVSF_COMPACT)) {
+    gprintf(gops, go, "; = ");
+    format_floating(gops, go, rv->f);
+    gprintf(gops, go, " s");
+  }
+}
+
+/* Duration type definition. */
+const struct tvec_regty tvty_duration = {
+  init_float, trivial_release, eq_float,
+  tobuf_float, frombuf_float,
+  parse_duration, dump_duration
+};
+
 /*----- Enumerations ------------------------------------------------------*/
 
 /* --- @init_tenum@ --- *
@@ -1908,7 +2058,7 @@ static int frombuf_penum(buf *b, union tvec_regval *rv,
 #define LITSTR_FLT     "literal floating-point number, "               \
                          "`#-inf', `#+inf', or `#nan'"
 #define FOUND_FLT      rv->f = a->f;
-#define MISSING_FLT    if (parse_floating(&rv->f, d.buf, ei->fi, tv))  \
+#define MISSING_FLT    if (parse_floating(&rv->f, 0, d.buf, ei->fi, tv)) \
                          { rc = -1; goto end; }
 
 #define LITSTR_PTR     "`#nil'"