pcompute dates
authorBen Pfaff <blp@cs.stanford.edu>
Sat, 9 Jul 2022 16:04:51 +0000 (09:04 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 9 Jul 2022 16:04:51 +0000 (09:04 -0700)
src/language/stats/ctables.c
tests/language/stats/ctables.at

index a25205e48d7e8d06a82a52fdec353ebe43b13017..aa204b4ea5f0070bd1aa1a582ac09a4d63e7bdeb 100644 (file)
@@ -580,7 +580,12 @@ struct ctables_category
             bool hide_subcategories; /* CCT_SUBTOTAL. */
           };
 
-        const struct ctables_postcompute *pc; /* CCT_POSTCOMPUTE. */
+        /* CCT_POSTCOMPUTE. */
+        struct
+          {
+            const struct ctables_postcompute *pc;
+            enum fmt_type parse_format;
+          };
 
         /* CCT_VALUE, CCT_LABEL, CCT_FUNCTION. */
         struct
@@ -1715,9 +1720,30 @@ ctables_table_parse_explicit_category (struct lexer *lexer,
   return true;
 }
 
+static bool
+parse_category_string (struct msg_location *location,
+                       struct substring s, const struct dictionary *dict,
+                       enum fmt_type format, double *n)
+{
+  union value v;
+  char *error = data_in (s, dict_get_encoding (dict), format,
+                         settings_get_fmt_settings (), &v, 0, NULL);
+  if (error)
+    {
+      msg_at (SE, location,
+              _("Failed to parse category specification as format %s: %s."),
+              fmt_name (format), error);
+      free (error);
+      return false;
+    }
+
+  *n = v.f;
+  return true;
+}
+
 static struct ctables_category *
-ctables_find_category_for_postcompute (const struct ctables_categories *cats,
-                                       const struct ctables_pcexpr *e)
+ctables_find_category_for_postcompute__ (const struct ctables_categories *cats,
+                                         const struct ctables_pcexpr *e)
 {
   struct ctables_category *best = NULL;
   size_t n_subtotals = 0;
@@ -1791,8 +1817,57 @@ ctables_find_category_for_postcompute (const struct ctables_categories *cats,
   return best;
 }
 
+static struct ctables_category *
+ctables_find_category_for_postcompute (const struct dictionary *dict,
+                                       const struct ctables_categories *cats,
+                                       enum fmt_type parse_format,
+                                       const struct ctables_pcexpr *e)
+{
+  if (parse_format != FMT_F)
+    {
+      if (e->op == CTPO_CAT_STRING)
+        {
+          double number;
+          if (!parse_category_string (e->location, e->string, dict,
+                                      parse_format, &number))
+            return NULL;
+
+          struct ctables_pcexpr e2 = {
+            .op = CTPO_CAT_NUMBER,
+            .number = number,
+            .location = e->location,
+          };
+          return ctables_find_category_for_postcompute__ (cats, &e2);
+        }
+      else if (e->op == CTPO_CAT_SRANGE)
+        {
+          double nrange[2];
+          if (!e->srange[0].string)
+            nrange[0] = -DBL_MAX;
+          else if (!parse_category_string (e->location, e->srange[0], dict,
+                                           parse_format, &nrange[0]))
+            return NULL;
+
+          if (!e->srange[1].string)
+            nrange[1] = DBL_MAX;
+          else if (!parse_category_string (e->location, e->srange[1], dict,
+                                           parse_format, &nrange[1]))
+            return NULL;
+
+          struct ctables_pcexpr e2 = {
+            .op = CTPO_CAT_NRANGE,
+            .nrange = { nrange[0], nrange[1] },
+            .location = e->location,
+          };
+          return ctables_find_category_for_postcompute__ (cats, &e2);
+        }
+    }
+  return ctables_find_category_for_postcompute__ (cats, e);
+}
+
 static bool
-ctables_recursive_check_postcompute (const struct ctables_pcexpr *e,
+ctables_recursive_check_postcompute (struct dictionary *dict,
+                                     const struct ctables_pcexpr *e,
                                      struct ctables_category *pc_cat,
                                      const struct ctables_categories *cats,
                                      const struct msg_location *cats_location)
@@ -1808,7 +1883,7 @@ ctables_recursive_check_postcompute (const struct ctables_pcexpr *e,
     case CTPO_CAT_TOTAL:
       {
         struct ctables_category *cat = ctables_find_category_for_postcompute (
-          cats, e);
+          dict, cats, pc_cat->parse_format, e);
         if (!cat)
           {
             if (e->op == CTPO_CAT_SUBTOTAL && e->subtotal_index == 0)
@@ -1861,7 +1936,7 @@ ctables_recursive_check_postcompute (const struct ctables_pcexpr *e,
     case CTPO_NEG:
       for (size_t i = 0; i < 2; i++)
         if (e->subs[i] && !ctables_recursive_check_postcompute (
-              e->subs[i], pc_cat, cats, cats_location))
+              dict, e->subs[i], pc_cat, cats, cats_location))
           return false;
       return true;
 
@@ -1870,27 +1945,6 @@ ctables_recursive_check_postcompute (const struct ctables_pcexpr *e,
     }
 }
 
-static bool
-parse_category_string (const struct ctables_category *cat,
-                       struct substring s, struct dictionary *dict,
-                       enum fmt_type format, double *n)
-{
-  union value v;
-  char *error = data_in (s, dict_get_encoding (dict), format,
-                         settings_get_fmt_settings (), &v, 0, NULL);
-  if (error)
-    {
-      msg_at (SE, cat->location,
-              _("Failed to parse category specification as format %s: %s."),
-              fmt_name (format), error);
-      free (error);
-      return false;
-    }
-
-  *n = v.f;
-  return true;
-}
-
 static bool
 all_strings (struct variable **vars, size_t n_vars,
              const struct ctables_category *cat)
@@ -1974,8 +2028,9 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
           switch (cat->type)
             {
             case CCT_POSTCOMPUTE:
-              if (!ctables_recursive_check_postcompute (cat->pc->expr, cat,
-                                                        c, cats_location))
+              cat->parse_format = parse_strings ? common_format->type : FMT_F;
+              if (!ctables_recursive_check_postcompute (dict, cat->pc->expr,
+                                                        cat, c, cats_location))
                 return false;
               break;
 
@@ -1998,7 +2053,7 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
               if (parse_strings)
                 {
                   double n;
-                  if (!parse_category_string (cat, cat->string, dict,
+                  if (!parse_category_string (cat->location, cat->string, dict,
                                               common_format->type, &n))
                     return false;
 
@@ -2018,13 +2073,15 @@ ctables_table_parse_categories (struct lexer *lexer, struct dictionary *dict,
 
                   if (!cat->srange[0].string)
                     n[0] = -DBL_MAX;
-                  else if (!parse_category_string (cat, cat->srange[0], dict,
+                  else if (!parse_category_string (cat->location,
+                                                   cat->srange[0], dict,
                                                    common_format->type, &n[0]))
                     return false;
 
                   if (!cat->srange[1].string)
                     n[1] = DBL_MAX;
-                  else if (!parse_category_string (cat, cat->srange[1], dict,
+                  else if (!parse_category_string (cat->location,
+                                                   cat->srange[1], dict,
                                                    common_format->type, &n[1]))
                     return false;
 
@@ -4070,6 +4127,7 @@ struct ctables_pcexpr_evaluate_ctx
     enum pivot_axis_type pc_a;
     size_t pc_a_idx;
     size_t summary_idx;
+    enum fmt_type parse_format;
   };
 
 static double ctables_pcexpr_evaluate (
@@ -4167,7 +4225,7 @@ ctables_pcexpr_evaluate (const struct ctables_pcexpr_evaluate_ctx *ctx,
     case CTPO_CAT_SRANGE:
       {
         struct ctables_cell_value cv = {
-          .category = ctables_find_category_for_postcompute (ctx->cats, e)
+          .category = ctables_find_category_for_postcompute (ctx->section->table->ctables->dict, ctx->cats, ctx->parse_format, e)
         };
         assert (cv.category != NULL);
 
@@ -4193,7 +4251,7 @@ ctables_pcexpr_evaluate (const struct ctables_pcexpr_evaluate_ctx *ctx,
     case CTPO_CAT_TOTAL:
       {
         struct ctables_cell_value cv = {
-          .category = ctables_find_category_for_postcompute (ctx->cats, e),
+          .category = ctables_find_category_for_postcompute (ctx->section->table->ctables->dict, ctx->cats, ctx->parse_format, e),
           .value = { .f = e->number },
         };
         assert (cv.category != NULL);
@@ -4222,29 +4280,28 @@ ctables_pcexpr_evaluate (const struct ctables_pcexpr_evaluate_ctx *ctx,
   NOT_REACHED ();
 }
 
-/* XXX what if there is a postcompute in more than one dimension?? */
-static const struct ctables_postcompute *
+static const struct ctables_category *
 ctables_cell_postcompute (const struct ctables_section *s,
                           const struct ctables_cell *cell,
                           enum pivot_axis_type *pc_a_p,
                           size_t *pc_a_idx_p)
 {
   assert (cell->postcompute);
-  const struct ctables_postcompute *pc = NULL;
+  const struct ctables_category *pc_cat = NULL;
   for (enum pivot_axis_type pc_a = 0; pc_a < PIVOT_N_AXES; pc_a++)
     for (size_t pc_a_idx = 0; pc_a_idx < s->nests[pc_a]->n; pc_a_idx++)
       {
         const struct ctables_cell_value *cv = &cell->axes[pc_a].cvs[pc_a_idx];
         if (cv->category->type == CCT_POSTCOMPUTE)
           {
-            if (pc)
+            if (pc_cat)
               {
                 /* Multiple postcomputes cross each other.  The value is
                    undefined. */
                 return NULL;
               }
 
-            pc = cv->category->pc;
+            pc_cat = cv->category;
             if (pc_a_p)
               *pc_a_p = pc_a;
             if (pc_a_idx_p)
@@ -4252,8 +4309,8 @@ ctables_cell_postcompute (const struct ctables_section *s,
           }
       }
 
-  assert (pc != NULL);
-  return pc;
+  assert (pc_cat != NULL);
+  return pc_cat;
 }
 
 static double
@@ -4266,11 +4323,12 @@ ctables_cell_calculate_postcompute (const struct ctables_section *s,
 {
   enum pivot_axis_type pc_a = 0;
   size_t pc_a_idx = 0;
-  const struct ctables_postcompute *pc = ctables_cell_postcompute (
+  const struct ctables_category *pc_cat = ctables_cell_postcompute (
     s, cell, &pc_a, &pc_a_idx);
-  if (!pc)
+  if (!pc_cat)
     return SYSMIS;
 
+  const struct ctables_postcompute *pc = pc_cat->pc;
   if (pc->specs)
     {
       for (size_t i = 0; i < pc->specs->n; i++)
@@ -4296,6 +4354,7 @@ ctables_cell_calculate_postcompute (const struct ctables_section *s,
     .pc_a = pc_a,
     .pc_a_idx = pc_a_idx,
     .summary_idx = summary_idx,
+    .parse_format = pc_cat->parse_format,
   };
   return ctables_pcexpr_evaluate (&ctx, pc->expr);
 }
index 65740d97b28794392fcd30cf86cee8431d92a94d..114d6dcc7cab590b6e1cb1088a931ec1435a2c55 100644 (file)
@@ -4,8 +4,6 @@ dnl Features not yet implemented:
 dnl
 dnl - Definition of columns/rows when labels are rotated from one axis to another.
 dnl - Preprocessing to distinguish categorical from scale.
-dnl - PCOMPUTE:
-dnl   * dates
 dnl
 dnl Features not yet tested:
 dnl - Parsing (positive and negative)
@@ -41,6 +39,7 @@ dnl   * PCOMPUTE for more than one kind of summary (e.g. [COUNT, ROWPCT]).
 dnl   * MISSING, OTHERNM
 dnl   * strings and string ranges
 dnl   * multi-dimensional (multiple CCT_POSTCOMPUTE in one cell)
+dnl   * dates
 dnl - PPROPERTIES:
 dnl   * )LABEL[N].
 dnl - Summary functions: