more validation
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 28 Dec 2021 02:43:03 +0000 (18:43 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 1 Jan 2022 19:17:32 +0000 (11:17 -0800)
src/language/stats/ctables.c

index 7b289ed2c78c479d7d414b658e6b1eefab7d4783..1b8ba198f83cca7c882e2b3140e729a698a7d270 100644 (file)
@@ -277,6 +277,12 @@ ctables_var_get_print_format (const struct ctables_var *var)
           : var_get_print_format (var->var));
 }
 
+static const char *
+ctables_var_name (const struct ctables_var *var)
+{
+  return var->is_mrset ? var->mrset->name : var_get_name (var->var);
+}
+
 struct ctables_categories
   {
     size_t n_refs;
@@ -410,6 +416,7 @@ struct ctables_axis
             bool scale;
             struct ctables_summary *summaries;
             size_t n_summaries;
+            size_t allocated_summaries;
           };
 
         /* Nonterminals. */
@@ -581,55 +588,104 @@ struct ctables_axis_parse_ctx
     struct ctables_table *t;
   };
 
-static struct ctables_summary *
-add_summary (struct ctables_axis *axis, enum ctables_summary_function function,
-             double percentile, size_t *allocated_summaries)
+static struct fmt_spec
+ctables_summary_default_format (enum ctables_summary_function function,
+                                const struct ctables_var *var)
 {
-  if (axis->n_summaries >= *allocated_summaries)
-    axis->summaries = x2nrealloc (axis->summaries, allocated_summaries,
-                                  sizeof *axis->summaries);
-
-  static const char *default_labels[] = {
-#define S(ENUM, NAME, LABEL, FORMAT, AVAILABILITY) [ENUM] = LABEL,
-    SUMMARIES
-#undef S
-  };
-  char *label = (function == CTSF_PTILE
-                 ? xasprintf (_("Percentile %.2f"), percentile)
-                 : xstrdup (gettext (default_labels[function])));
-
   static const enum ctables_format default_formats[] = {
 #define S(ENUM, NAME, LABEL, FORMAT, AVAILABILITY) [ENUM] = FORMAT,
     SUMMARIES
 #undef S
   };
-  struct fmt_spec format;
   switch (default_formats[function])
     {
     case CTF_COUNT:
-      format = (struct fmt_spec) { .type = FMT_F, .w = 40 };
-      break;
+      return (struct fmt_spec) { .type = FMT_F, .w = 40 };
 
     case CTF_PERCENT:
-      format = (struct fmt_spec) { .type = FMT_PCT, .w = 40, .d = 1 };
-      break;
+      return (struct fmt_spec) { .type = FMT_PCT, .w = 40, .d = 1 };
 
     case CTF_GENERAL:
-      format = *ctables_var_get_print_format (&axis->var);
-      break;
+      return *ctables_var_get_print_format (var);
 
     default:
       NOT_REACHED ();
     }
+}
 
-  struct ctables_summary *s = &axis->summaries[axis->n_summaries++];
-  *s = (struct ctables_summary) {
-    .function = function,
-    .percentile = percentile,
-    .label = label,
-    .format = format,
+static const char *
+ctables_summary_function_name (enum ctables_summary_function function)
+{
+  static const char *names[] = {
+#define S(ENUM, NAME, LABEL, FORMAT, AVAILABILITY) [ENUM] = NAME,
+    SUMMARIES
+#undef S
   };
-  return s;
+  return names[function];
+}
+
+static bool
+add_summary (struct ctables_axis *axis,
+             enum ctables_summary_function function, double percentile,
+             const char *label, const struct fmt_spec *format,
+             const struct msg_location *loc)
+{
+  if (axis->op == CTAO_VAR)
+    {
+      if (axis->n_summaries >= axis->allocated_summaries)
+        axis->summaries = x2nrealloc (axis->summaries,
+                                      &axis->allocated_summaries,
+                                      sizeof *axis->summaries);
+
+      const char *function_name = ctables_summary_function_name (function);
+      const char *var_name = ctables_var_name (&axis->var);
+      switch (ctables_function_availability (function))
+        {
+        case CTFA_MRSETS:
+          if (!axis->var.is_mrset)
+            {
+              msg_at (SE, loc, _("Summary function %s applies only to multiple "
+                                 "response sets."), function_name);
+              msg_at (SN, axis->loc, _("'%s' is not a multiple response set."),
+                      var_name);
+              return false;
+            }
+          break;
+
+        case CTFA_SCALE:
+          if (!axis->scale)
+            {
+              msg_at (SE, loc,
+                      _("Summary function %s applies only to scale variables."),
+                      function_name);
+              msg_at (SN, axis->loc, _("'%s' is not a scale variable."),
+                      var_name);
+              return false;
+            }
+          break;
+
+        case CTFA_ALL:
+          break;
+        }
+
+      struct ctables_summary *dst = &axis->summaries[axis->n_summaries++];
+      *dst = (struct ctables_summary) {
+        .function = function,
+        .percentile = percentile,
+        .label = xstrdup (label),
+        .format = (format ? *format
+                   : ctables_summary_default_format (function, &axis->var)),
+      };
+      return true;
+    }
+  else
+    {
+      for (size_t i = 0; i < 2; i++)
+        if (!add_summary (axis->subs[i], function, percentile, label, format,
+                          loc))
+          return false;
+      return true;
+    }
 }
 
 static struct ctables_axis *ctables_axis_parse_stack (
@@ -697,48 +753,85 @@ ctables_axis_parse_primary (struct ctables_axis_parse_ctx *ctx)
                  : var_get_measure (var.var) == MEASURE_SCALE);
   axis->loc = lex_ofs_location (ctx->lexer, start_ofs,
                                 lex_ofs (ctx->lexer) - 1);
+  return axis;
+}
 
-  if (lex_match (ctx->lexer, T_LBRACK))
+static struct ctables_axis *
+ctables_axis_parse_postfix (struct ctables_axis_parse_ctx *ctx)
+{
+  struct ctables_axis *sub = ctables_axis_parse_primary (ctx);
+  if (!sub || !lex_match (ctx->lexer, T_LBRACK))
+    return sub;
+
+  do
     {
-      size_t allocated_summaries = 0;
-      do
+      int start_ofs = lex_ofs (ctx->lexer);
+
+      /* Parse function. */
+      enum ctables_summary_function function;
+      if (!parse_ctables_summary_function (ctx->lexer, &function))
+        goto error;
+
+      /* Parse percentile. */
+      double percentile = 0;
+      if (function == CTSF_PTILE)
         {
-          enum ctables_summary_function function;
-          if (!parse_ctables_summary_function (ctx->lexer, &function))
+          if (!lex_force_num_range_closed (ctx->lexer, "PTILE", 0, 100))
             goto error;
+          percentile = lex_number (ctx->lexer);
+          lex_get (ctx->lexer);
+        }
 
-          double percentile = 0;
-          if (function == CTSF_PTILE)
-            {
-              if (!lex_force_num_range_closed (ctx->lexer, "PTILE", 0, 100))
-                goto error;
-              percentile = lex_number (ctx->lexer);
-              lex_get (ctx->lexer);
-            }
+      /* Parse label. */
+      char *label;
+      if (lex_is_string (ctx->lexer))
+        {
+          label = ss_xstrdup (lex_tokss (ctx->lexer));
+          lex_get (ctx->lexer);
+        }
+      else if (function == CTSF_PTILE)
+        label = xasprintf (_("Percentile %.2f"), percentile);
+      else
+        {
+          static const char *default_labels[] = {
+#define S(ENUM, NAME, LABEL, FORMAT, AVAILABILITY) [ENUM] = LABEL,
+            SUMMARIES
+#undef S
+          };
+          label = xstrdup (gettext (default_labels[function]));
+        }
 
-          struct ctables_summary *s = add_summary (axis, function, percentile,
-                                                   &allocated_summaries);
-          if (lex_is_string (ctx->lexer))
-            {
-              free (s->label);
-              s->label = ss_xstrdup (lex_tokss (ctx->lexer));
-              lex_get (ctx->lexer);
-            }
-          if (lex_token (ctx->lexer) == T_ID)
+      /* Parse format. */
+      struct fmt_spec format;
+      const struct fmt_spec *formatp;
+      if (lex_token (ctx->lexer) == T_ID)
+        {
+          if (!parse_format_specifier (ctx->lexer, &format)
+              || !fmt_check_output (&format)
+              || !fmt_check_type_compat (&format, VAL_NUMERIC))
             {
-              if (!parse_format_specifier (ctx->lexer, &s->format)
-                  || !fmt_check_output (&s->format)
-                  || !fmt_check_type_compat (&s->format, VAL_NUMERIC))
-                goto error;
+              free (label);
+              goto error;
             }
-          lex_match (ctx->lexer, T_COMMA);
+          formatp = &format;
         }
-      while (!lex_match (ctx->lexer, T_RBRACK));
+      else
+        formatp = NULL;
+
+      struct msg_location *loc = lex_ofs_location (ctx->lexer, start_ofs,
+                                                   lex_ofs (ctx->lexer) - 1);
+      add_summary (sub, function, percentile, label, formatp, loc);
+      free (label);
+      msg_location_destroy (loc);
+
+      lex_match (ctx->lexer, T_COMMA);
     }
-  return axis;
+  while (!lex_match (ctx->lexer, T_RBRACK));
+
+  return sub;
 
 error:
-  ctables_axis_destroy (axis);
+  ctables_axis_destroy (sub);
   return NULL;
 }
 
@@ -793,13 +886,13 @@ static struct ctables_axis *
 ctables_axis_parse_nest (struct ctables_axis_parse_ctx *ctx)
 {
   int start_ofs = lex_ofs (ctx->lexer);
-  struct ctables_axis *lhs = ctables_axis_parse_primary (ctx);
+  struct ctables_axis *lhs = ctables_axis_parse_postfix (ctx);
   if (!lhs)
     return NULL;
 
   while (lex_match (ctx->lexer, T_GT))
     {
-      struct ctables_axis *rhs = ctables_axis_parse_primary (ctx);
+      struct ctables_axis *rhs = ctables_axis_parse_postfix (ctx);
       if (!rhs)
         return NULL;