figure out frequency table structure
[pspp] / src / language / stats / ctables.c
index 7b289ed2c78c479d7d414b658e6b1eefab7d4783..d71754d01e88c1974dbb62abb9766a4a184422b1 100644 (file)
@@ -26,6 +26,7 @@
 #include "libpspp/assertion.h"
 #include "libpspp/hmap.h"
 #include "libpspp/message.h"
+#include "libpspp/string-array.h"
 #include "output/pivot-table.h"
 
 #include "gl/minmax.h"
@@ -216,12 +217,7 @@ struct ctables_postcompute_expr
         /* CTPO_CAT_RANGE.
 
            XXX what about string ranges? */
-        struct
-          {
-            double low;         /* -DBL_MAX for LO. */
-            double high;        /* DBL_MAX for HIGH. */
-          }
-        range;
+        double range[2];
 
         /* CTPO_ADD, CTPO_SUB, CTPO_MUL, CTPO_DIV, CTPO_POW. */
         struct ctables_postcompute_expr *subs[2];
@@ -277,6 +273,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 +412,7 @@ struct ctables_axis
             bool scale;
             struct ctables_summary *summaries;
             size_t n_summaries;
+            size_t allocated_summaries;
           };
 
         /* Nonterminals. */
@@ -581,55 +584,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 +749,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 +882,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;
 
@@ -1159,6 +1248,77 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
   return true;
 }
 
+struct ctables_freqtab
+  {
+    struct variable **vars;
+    size_t n_vars;
+
+    struct hmap data;           /* Contains "struct freq"s. */
+  };
+
+static struct string_array
+nest_fts (struct string_array sa0, struct string_array sa1)
+{
+  if (!sa0.n)
+    return sa1;
+  else if (!sa1.n)
+    return sa0;
+
+  struct string_array sa = STRING_ARRAY_INITIALIZER;
+  for (size_t i = 0; i < sa0.n; i++)
+    for (size_t j = 0; j < sa1.n; j++)
+      string_array_append_nocopy (&sa, xasprintf ("%s, %s",
+                                                  sa0.strings[i],
+                                                  sa1.strings[j]));
+  string_array_destroy (&sa0);
+  string_array_destroy (&sa1);
+  return sa;
+}
+
+static struct string_array
+enumerate_fts (const struct ctables_axis *a)
+{
+  struct string_array sa = STRING_ARRAY_INITIALIZER;
+  if (!a)
+    return sa;
+
+  switch (a->op)
+    {
+    case CTAO_VAR:
+      string_array_append (&sa, ctables_var_name (&a->var));
+      break;
+
+    case CTAO_STACK:
+      sa = enumerate_fts (a->subs[0]);
+      struct string_array sa2 = enumerate_fts (a->subs[1]);
+      for (size_t i = 0; i < sa2.n; i++)
+        string_array_append_nocopy (&sa, sa2.strings[i]);
+      free (sa2.strings);
+      break;
+
+    case CTAO_NEST:
+      return nest_fts (enumerate_fts (a->subs[0]),
+                       enumerate_fts (a->subs[1]));
+    }
+  return sa;
+}
+
+static void
+ctables_execute (struct ctables *ct)
+{
+  for (size_t i = 0; i < ct->n_tables; i++)
+    {
+      struct ctables_table *t = &ct->tables[i];
+      struct string_array sa = enumerate_fts (t->axes[PIVOT_AXIS_ROW]);
+      sa = nest_fts (sa, enumerate_fts (t->axes[PIVOT_AXIS_COLUMN]));
+      sa = nest_fts (sa, enumerate_fts (t->axes[PIVOT_AXIS_LAYER]));
+      for (size_t i = 0; i < sa.n; i++)
+        puts (sa.strings[i]);
+      putc ('\n', stdout);
+      string_array_destroy (&sa);
+    }
+}
+
 int
 cmd_ctables (struct lexer *lexer, struct dataset *ds)
 {
@@ -1433,7 +1593,6 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
       if (!lex_force_match (lexer, T_SLASH))
         break;
 
-      /* XXX Validate axes. */
       while (!lex_match_id (lexer, "TABLE") && lex_token (lexer) != T_ENDCMD)
         {
           if (lex_match_id (lexer, "SLABELS"))
@@ -1764,8 +1923,17 @@ cmd_ctables (struct lexer *lexer, struct dataset *ds)
               goto error;
             }
         }
+
+      if (t->row_labels != CTLP_NORMAL && t->col_labels != CTLP_NORMAL)
+        {
+          msg (SE, _("ROWLABELS and COLLABELS may not both be specified."));
+          goto error;
+        }
+
     }
   while (lex_token (lexer) != T_ENDCMD);
+
+  ctables_execute (ct);
   ctables_destroy (ct);
   return CMD_SUCCESS;