Change how checking for missing values works.
[pspp] / src / data / missing-values.c
index 07ebb77a17312a388ced25b3f04e2ae4f4f0fea5..44d0bf9ee67315148224715f3ae4f083019c3636 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2005 Free Software Foundation, Inc.
+   Copyright (C) 2005, 2009, 2011, 2013 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
-#include "missing-values.h"
+
+#include "data/missing-values.h"
+
 #include <assert.h>
 #include <stdlib.h>
-#include <libpspp/array.h>
-#include <libpspp/assertion.h>
-#include "variable.h"
-#include <libpspp/str.h>
+
+#include "data/variable.h"
+#include "libpspp/assertion.h"
+#include "libpspp/cast.h"
+#include "libpspp/i18n.h"
+#include "libpspp/str.h"
+
+#include "gl/minmax.h"
 
 /* Types of user-missing values.
    Invisible--use access functions defined below instead. */
@@ -36,15 +42,47 @@ enum mv_type
   };
 
 /* Initializes MV as a set of missing values for a variable of
-   the given WIDTH.  Although only numeric variables and short
-   string variables may have missing values, WIDTH may be any
-   valid variable width. */
+   the given WIDTH.  MV should be destroyed with mv_destroy when
+   it is no longer needed. */
 void
 mv_init (struct missing_values *mv, int width)
 {
+  int i;
+
+  assert (width >= 0 && width <= MAX_STRING);
+  mv->type = MVT_NONE;
+  mv->width = width;
+  for (i = 0; i < 3; i++)
+    value_init (&mv->values[i], width);
+}
+
+/* Initializes MV as a set of missing values for a variable of
+   the given WIDTH.  MV will be automatically destroyed along
+   with POOL; it must not be passed to mv_destroy for explicit
+   destruction. */
+void
+mv_init_pool (struct pool *pool, struct missing_values *mv, int width)
+{
+  int i;
+
   assert (width >= 0 && width <= MAX_STRING);
   mv->type = MVT_NONE;
   mv->width = width;
+  for (i = 0; i < 3; i++)
+    value_init_pool (pool, &mv->values[i], width);
+}
+
+/* Frees any storage allocated by mv_init for MV. */
+void
+mv_destroy (struct missing_values *mv)
+{
+  if (mv != NULL)
+    {
+      int i;
+
+      for (i = 0; i < 3; i++)
+        value_destroy (&mv->values[i], mv->width);
+    }
 }
 
 /* Removes any missing values from MV. */
@@ -54,13 +92,32 @@ mv_clear (struct missing_values *mv)
   mv->type = MVT_NONE;
 }
 
-/* Copies SRC to MV. */
+/* Initializes MV as a copy of SRC. */
 void
 mv_copy (struct missing_values *mv, const struct missing_values *src)
 {
-  assert(src);
+  int i;
 
-  *mv = *src;
+  mv_init (mv, src->width);
+  mv->type = src->type;
+  for (i = 0; i < 3; i++)
+    value_copy (&mv->values[i], &src->values[i], mv->width);
+}
+
+/* Returns true if VALUE, of the given WIDTH, may be added to a
+   missing value set also of the given WIDTH.  This is normally
+   the case, but string missing values over MV_MAX_STRING bytes
+   long must consist solely of spaces after the first
+   MV_MAX_STRING bytes.  */
+bool
+mv_is_acceptable (const union value *value, int width)
+{
+  int i;
+
+  for (i = MV_MAX_STRING; i < width; i++)
+    if (value->s[i] != ' ')
+      return false;
+  return true;
 }
 
 /* Returns true if MV is an empty set of missing values. */
@@ -80,20 +137,21 @@ mv_get_width (const struct missing_values *mv)
 
 /* Attempts to add individual value V to the set of missing
    values MV.  Returns true if successful, false if MV has no
-   more room for missing values.  (Long string variables never
-   accept missing values.) */
+   more room for missing values or if V is not an acceptable
+   missing value. */
 bool
 mv_add_value (struct missing_values *mv, const union value *v)
 {
-  if (mv->width > MAX_SHORT_STRING)
+  if (!mv_is_acceptable (v, mv->width))
     return false;
+
   switch (mv->type)
     {
     case MVT_NONE:
     case MVT_1:
     case MVT_2:
     case MVT_RANGE:
-      mv->values[mv->type & 3] = *v;
+      value_copy (&mv->values[mv->type & 3], v, mv->width);
       mv->type++;
       return true;
 
@@ -104,16 +162,27 @@ mv_add_value (struct missing_values *mv, const union value *v)
   NOT_REACHED ();
 }
 
-/* Attempts to add S to the set of string missing values MV.  S
-   must contain exactly as many characters as MV's width.
-   Returns true if successful, false if MV has no more room for
-   missing values.  (Long string variables never accept missing
-   values.) */
+/* Attempts to add S, which is LEN bytes long, to the set of string missing
+   values MV.  Returns true if successful, false if MV has no more room for
+   missing values or if S is not an acceptable missing value. */
 bool
-mv_add_str (struct missing_values *mv, const char s[])
+mv_add_str (struct missing_values *mv, const uint8_t s[], size_t len)
 {
+  union value v;
+  bool ok;
+
   assert (mv->width > 0);
-  return mv_add_value (mv, (union value *) s);
+  while (len > mv->width)
+    if (s[--len] != ' ')
+      return false;
+
+  value_init (&v, mv->width);
+  buf_copy_rpad (CHAR_CAST (char *, v.s), mv->width,
+                 CHAR_CAST (char *, s), len, ' ');
+  ok = mv_add_value (mv, &v);
+  value_destroy (&v, mv->width);
+
+  return ok;
 }
 
 /* Attempts to add D to the set of numeric missing values MV.
@@ -122,8 +191,16 @@ mv_add_str (struct missing_values *mv, const char s[])
 bool
 mv_add_num (struct missing_values *mv, double d)
 {
+  union value v;
+  bool ok;
+
   assert (mv->width == 0);
-  return mv_add_value (mv, (union value *) &d);
+  value_init (&v, 0);
+  v.f = d;
+  ok = mv_add_value (mv, &v);
+  value_destroy (&v, 0);
+
+  return ok;
 }
 
 /* Attempts to add range [LOW, HIGH] to the set of numeric
@@ -152,7 +229,8 @@ mv_has_value (const struct missing_values *mv)
   return mv_n_values (mv) > 0;
 }
 
-/* Removes one individual value from MV and stores it in *V.
+/* Removes one individual value from MV and stores it in V, which
+   must have been initialized as a value with the same width as MV.
    MV must contain an individual value (as determined by
    mv_has_value()).
 
@@ -165,32 +243,46 @@ mv_has_value (const struct missing_values *mv)
 void
 mv_pop_value (struct missing_values *mv, union value *v)
 {
+  union value tmp;
+
   assert (mv_has_value (mv));
 
-  *v = mv->values[0];
-  remove_element (mv->values, mv->type & 3, sizeof *mv->values, 0);
+  value_copy (v, &mv->values[0], mv->width);
+  tmp = mv->values[0];
+  mv->values[0] = mv->values[1];
+  mv->values[1] = mv->values[2];
+  mv->values[2] = tmp;
   mv->type--;
 }
 
-/* Stores MV's value with index IDX in *V.
+/* Returns MV's discrete value with index IDX.  The caller must
+   not modify or free this value, or access it after MV is
+   modified or freed.
    IDX must be less than the number of discrete values in MV, as
-   reported by mv_n_values(MV). */
-void
-mv_get_value (const struct missing_values *mv, union value *v, int idx)
+   reported by mv_n_values. */
+const union value *
+mv_get_value (const struct missing_values *mv, int idx)
 {
   assert (idx >= 0 && idx < mv_n_values (mv));
-  *v = mv->values[idx];
+  return &mv->values[idx];
 }
 
-void
+/* Replaces MV's discrete value with index IDX by a copy of V,
+   which must have the same width as MV.
+   IDX must be less than the number of discrete values in MV, as
+   reported by mv_n_values. */
+bool
 mv_replace_value (struct missing_values *mv, const union value *v, int idx)
 {
   assert (idx >= 0) ;
   assert (idx < mv_n_values(mv));
 
-  mv->values[idx] = *v;
-}
+  if (!mv_is_acceptable (v, mv->width))
+    return false;
 
+  value_copy (&mv->values[idx], v, mv->width);
+  return true;
+}
 
 /* Returns the number of individual (not part of a range) missing
    values in MV. */
@@ -232,7 +324,6 @@ mv_get_range (const struct missing_values *mv, double *low, double *high)
   *high = mv->values[2].f;
 }
 
-
 /* Returns true if values[IDX] is in use when the `type' member
    is set to TYPE (in struct missing_values),
    false otherwise. */
@@ -262,17 +353,12 @@ using_element (unsigned type, int idx)
 /* Returns true if MV can be resized to the given WIDTH with
    mv_resize(), false otherwise.  Resizing is possible only when
    each value in MV (if any) is resizable from MV's current width
-   to WIDTH, as determined by value_is_resizable.  In addition,
-   resizing must not produce a non-empty set of long string
-   missing values. */
+   to WIDTH, as determined by value_is_resizable. */
 bool
 mv_is_resizable (const struct missing_values *mv, int width)
 {
   int i;
 
-  if (width > MAX_SHORT_STRING && mv->type != MVT_NONE)
-    return false;
-
   for (i = 0; i < 3; i++)
     if (using_element (mv->type, i)
         && !value_is_resizable (&mv->values[i], mv->width, width))
@@ -292,6 +378,11 @@ mv_resize (struct missing_values *mv, int width)
   for (i = 0; i < 3; i++)
     if (using_element (mv->type, i))
       value_resize (&mv->values[i], mv->width, width);
+    else
+      {
+        value_destroy (&mv->values[i], mv->width);
+        value_init (&mv->values[i], width);
+      }
   mv->width = width;
 }
 
@@ -324,8 +415,7 @@ is_num_user_missing (const struct missing_values *mv, double d)
    MV must be a set of string missing values.
    S[] must contain exactly as many characters as MV's width. */
 static bool
-is_str_user_missing (const struct missing_values *mv,
-                        const char s[])
+is_str_user_missing (const struct missing_values *mv, const uint8_t s[])
 {
   const union value *v = mv->values;
   assert (mv->width > 0);
@@ -349,37 +439,97 @@ is_str_user_missing (const struct missing_values *mv,
   NOT_REACHED ();
 }
 
-/* Returns true if V is a missing value in the given CLASS in MV,
-   false otherwise. */
-bool
-mv_is_value_missing (const struct missing_values *mv, const union value *v,
-                     enum mv_class class)
+/* Returns MV_USER if V is a user-missing value in MV, MV_SYSTEM if V is
+   system-missing (and MV is numeric), or 0 if V is not missing. */
+enum mv_class
+mv_is_value_missing (const struct missing_values *mv, const union value *v)
 {
   return (mv->width == 0
-          ? mv_is_num_missing (mv, v->f, class)
-          : mv_is_str_missing (mv, v->s, class));
+          ? mv_is_num_missing (mv, v->f)
+          : mv_is_str_missing (mv, v->s));
 }
 
-/* Returns true if D is a missing value in the given CLASS in MV,
-   false otherwise.
-   MV must be a set of numeric missing values. */
-bool
-mv_is_num_missing (const struct missing_values *mv, double d,
-                   enum mv_class class)
+/* Returns MV_USER if V is a user-missing value in MV, MV_SYSTEM if V is
+   system-missing, or 0 if V is not missing.  MV must be a set of numeric
+   missing values. */
+enum mv_class
+mv_is_num_missing (const struct missing_values *mv, double d)
 {
   assert (mv->width == 0);
-  return ((class & MV_SYSTEM && d == SYSMIS)
-          || (class & MV_USER && is_num_user_missing (mv, d)));
+  return (d == SYSMIS ? MV_SYSTEM
+          : is_num_user_missing (mv, d) ? MV_USER
+          : 0);
 }
 
-/* Returns true if S[] is a missing value in the given CLASS in
-   MV, false otherwise.
-   MV must be a set of string missing values.
-   S[] must contain exactly as many characters as MV's width. */
-bool
-mv_is_str_missing (const struct missing_values *mv, const char s[],
-                   enum mv_class class)
+/* Returns MV_USER if S[] is a user-missing value in MV, or 0 if V is not
+   missing.  MV must be a set of string missing values.  S[] must contain
+   exactly as many characters as MV's width. */
+enum mv_class
+mv_is_str_missing (const struct missing_values *mv, const uint8_t s[])
 {
   assert (mv->width > 0);
-  return class & MV_USER && is_str_user_missing (mv, s);
+  return is_str_user_missing (mv, s) ? MV_USER : 0;
+}
+
+/* Like mv_is_value_missing(), this tests whether V is a missing value in MV.
+   It supports the uncommon case where V and MV might have different widths:
+   the caller must specify VW, the width of V.  MV and VW must be both numeric
+   or both string.
+
+   Comparison of strings of different width is done by conceptually extending
+   both strings to infinite width by appending spaces. */
+enum mv_class
+mv_is_value_missing_varwidth (const struct missing_values *mv,
+                              const union value *v, int vw)
+{
+  int mvw = mv->width;
+  if (mvw == vw)
+    return mv_is_value_missing (mv, v);
+
+  /* Make sure they're both strings. */
+  assert (mvw && vw);
+  if (mv->type == MVT_NONE)
+    return false;
+
+  for (int i = 0; i < mv->type; i++)
+    if (!buf_compare_rpad (CHAR_CAST_BUG (const char *, mv->values[i].s), mvw,
+                           CHAR_CAST_BUG (const char *, v->s), vw))
+      return MV_USER;
+  return 0;
+}
+
+char *
+mv_to_string (const struct missing_values *mv, const char *encoding)
+{
+  struct string s = DS_EMPTY_INITIALIZER;
+  if (mv_has_range (mv))
+    {
+      double x, y;
+      mv_get_range (mv, &x, &y);
+      if (x == LOWEST)
+        ds_put_format (&s, "LOWEST THRU %.*g", DBL_DIG + 1, y);
+      else if (y == HIGHEST)
+        ds_put_format (&s, "%.*g THRU HIGHEST", DBL_DIG + 1, x);
+      else
+        ds_put_format (&s, "%.*g THRU %.*g",
+                       DBL_DIG + 1, x,
+                       DBL_DIG + 1, y);
+    }
+  for (size_t j = 0; j < mv_n_values (mv); j++)
+    {
+      const union value *value = mv_get_value (mv, j);
+      if (!ds_is_empty (&s))
+        ds_put_cstr (&s, "; ");
+      if (!mv->width)
+        ds_put_format (&s, "%.*g", DBL_DIG + 1, value->f);
+      else
+        {
+          char *mvs = recode_string (
+            "UTF-8", encoding, CHAR_CAST (char *, value->s),
+            MIN (mv->width, MV_MAX_STRING));
+          ds_put_format (&s, "\"%s\"", mvs);
+          free (mvs);
+        }
+    }
+  return ds_is_empty (&s) ? NULL : ds_steal_cstr (&s);
 }