Add support for variable sets in the system file format.
[pspp] / src / data / dictionary.c
index 66cb956f1dc6d1dfa6284fe5bcc9d601e110d2b3..d69f1c29017c61c7d4842de8e11cce765f757c9e 100644 (file)
@@ -32,6 +32,7 @@
 #include "data/value-labels.h"
 #include "data/vardict.h"
 #include "data/variable.h"
+#include "data/varset.h"
 #include "data/vector.h"
 #include "libpspp/array.h"
 #include "libpspp/assertion.h"
@@ -78,6 +79,8 @@ struct dictionary
     struct attrset attributes;  /* Custom attributes. */
     struct mrset **mrsets;      /* Multiple response sets. */
     size_t n_mrsets;            /* Number of multiple response sets. */
+    struct varset **varsets;    /* Variable sets. */
+    size_t n_varsets;           /* Number of variable sets. */
 
     /* Whether variable names must be valid identifiers.  Normally, this is
        true, but sometimes a dictionary is prepared for external use
@@ -96,6 +99,7 @@ struct dictionary
 
 static void dict_unset_split_var (struct dictionary *, struct variable *, bool);
 static void dict_unset_mrset_var (struct dictionary *, struct variable *);
+static void dict_unset_varset_var (struct dictionary *, struct variable *);
 
 /* Compares two double pointers to variables, which should point
    to elements of a struct dictionary's `var' member array. */
@@ -179,15 +183,35 @@ dict_get_encoding (const struct dictionary *d)
   return d->encoding ;
 }
 
+/* Checks whether UTF-8 string ID is an acceptable identifier in DICT's
+   encoding.  Returns true if it is, otherwise an error message that the caller
+   must free(). */
+char * WARN_UNUSED_RESULT
+dict_id_is_valid__ (const struct dictionary *dict, const char *id)
+{
+  if (!dict->names_must_be_ids)
+    return NULL;
+  return id_is_valid__ (id, dict->encoding);
+}
+
+static bool
+error_to_bool (char *error)
+{
+  if (error)
+    {
+      free (error);
+      return false;
+    }
+  else
+    return true;
+}
+
 /* Returns true if UTF-8 string ID is an acceptable identifier in DICT's
-   encoding, false otherwise.  If ISSUE_ERROR is true, issues an explanatory
-   error message on failure. */
+   encoding, false otherwise. */
 bool
-dict_id_is_valid (const struct dictionary *dict, const char *id,
-                  bool issue_error)
+dict_id_is_valid (const struct dictionary *dict, const char *id)
 {
-  return (!dict->names_must_be_ids
-          || id_is_valid (id, dict->encoding, issue_error));
+  return error_to_bool (dict_id_is_valid__ (dict, id));
 }
 
 void
@@ -279,20 +303,16 @@ dict_create (const char *encoding)
 struct dictionary *
 dict_clone (const struct dictionary *s)
 {
-  struct dictionary *d;
-  size_t i;
-
-  d = dict_create (s->encoding);
+  struct dictionary *d = dict_create (s->encoding);
   dict_set_names_must_be_ids (d, dict_get_names_must_be_ids (s));
 
-  for (i = 0; i < s->n_vars; i++)
+  for (size_t i = 0; i < s->n_vars; i++)
     {
       struct variable *sv = s->vars[i].var;
       struct variable *dv = dict_clone_var_assert (d, sv);
-      size_t i;
 
-      for (i = 0; i < var_get_n_short_names (sv); i++)
-        var_set_short_name (dv, i, var_get_short_name (sv, i));
+      for (size_t j = 0; j < var_get_n_short_names (sv); j++)
+        var_set_short_name (dv, j, var_get_short_name (sv, j));
 
       var_get_vardict (dv)->case_index = var_get_vardict (sv)->case_index;
     }
@@ -303,7 +323,7 @@ dict_clone (const struct dictionary *s)
   if (d->n_splits > 0)
     {
        d->split = xnmalloc (d->n_splits, sizeof *d->split);
-       for (i = 0; i < d->n_splits; i++)
+       for (size_t i = 0; i < d->n_splits; i++)
          d->split[i] = dict_lookup_var_assert (d, var_get_name (s->split[i]));
     }
   d->split_type = s->split_type;
@@ -320,12 +340,12 @@ dict_clone (const struct dictionary *s)
 
   d->n_vectors = s->n_vectors;
   d->vector = xnmalloc (d->n_vectors, sizeof *d->vector);
-  for (i = 0; i < s->n_vectors; i++)
+  for (size_t i = 0; i < s->n_vectors; i++)
     d->vector[i] = vector_clone (s->vector[i], s, d);
 
   dict_set_attributes (d, dict_get_attributes (s));
 
-  for (i = 0; i < s->n_mrsets; i++)
+  for (size_t i = 0; i < s->n_mrsets; i++)
     {
       const struct mrset *old = s->mrsets[i];
       struct mrset *new;
@@ -339,10 +359,20 @@ dict_clone (const struct dictionary *s)
       dict_add_mrset (d, new);
     }
 
-  return d;
-}
+  for (size_t i = 0; i < s->n_varsets; i++)
+    {
+      const struct varset *old = s->varsets[i];
 
+      /* Clone old varset, then replace vars from D by vars from S. */
+      struct varset *new = varset_clone (old);
+      for (size_t j = 0; j < new->n_vars; j++)
+        new->vars[j] = dict_lookup_var_assert (d, var_get_name (new->vars[j]));
+
+      dict_add_varset (d, new);
+    }
 
+  return d;
+}
 \f
 /* Returns the SPLIT FILE vars (see cmd_split_file()).  Call
    dict_get_n_splits() to determine how many SPLIT FILE vars
@@ -384,28 +414,33 @@ dict_unset_split_var (struct dictionary *d, struct variable *v, bool skip_callba
 }
 
 
-/* Sets N split vars SPLIT in dictionary D. */
+/* Sets N split vars SPLIT in dictionary D.  N is silently capped to a maximum
+   of MAX_SPLITS. */
 static void
 dict_set_split_vars__ (struct dictionary *d,
                        struct variable *const *split, size_t n,
                        enum split_type type, bool skip_callbacks)
 {
+  if (n > MAX_SPLITS)
+    n = MAX_SPLITS;
   assert (n == 0 || split != NULL);
 
   d->n_splits = n;
-  d->split_type = type == SPLIT_NONE ? SPLIT_LAYERED : type;
+  d->split_type = (n == 0 ? SPLIT_NONE
+                   : type == SPLIT_NONE ? SPLIT_LAYERED
+                   : type);
   if (n > 0)
-   {
-    d->split = xnrealloc (d->split, n, sizeof *d->split) ;
-    memcpy (d->split, split, n * sizeof *d->split);
-   }
+    {
+      d->split = xnrealloc (d->split, n, sizeof *d->split) ;
+      memcpy (d->split, split, n * sizeof *d->split);
+    }
   else
-   {
-    free (d->split);
-    d->split = NULL;
-   }
+    {
+      free (d->split);
+      d->split = NULL;
+    }
 
- if (!skip_callbacks)
 if (!skip_callbacks)
     {
       if (d->changed) d->changed (d, d->changed_data);
       if (d->callbacks &&  d->callbacks->split_changed)
@@ -458,6 +493,7 @@ dict_delete_var__ (struct dictionary *d, struct variable *v, bool skip_callbacks
 
   dict_unset_split_var (d, v, skip_callbacks);
   dict_unset_mrset_var (d, v);
+  dict_unset_varset_var (d, v);
 
   if (d->weight == v)
     dict_set_weight (d, NULL);
@@ -554,6 +590,7 @@ dict_delete_consecutive_vars (struct dictionary *d, size_t idx, size_t count)
 
       dict_unset_split_var (d, v, false);
       dict_unset_mrset_var (d, v);
+      dict_unset_varset_var (d, v);
 
       if (d->weight == v)
        dict_set_weight (d, NULL);
@@ -670,6 +707,7 @@ _dict_destroy (struct dictionary *d)
   hmap_destroy (&d->name_map);
   attrset_destroy (&d->attributes);
   dict_clear_mrsets (d);
+  dict_clear_varsets (d);
   free (d->encoding);
   free (d);
 }
@@ -1290,10 +1328,10 @@ dict_get_rounded_case_weight (const struct dictionary *d,
 }
 
 /* Returns the format to use for weights. */
-const struct fmt_spec *
+struct fmt_spec
 dict_get_weight_format (const struct dictionary *d)
 {
-  return d->weight ? var_get_print_format (d->weight) : &F_8_0;
+  return d->weight ? var_get_print_format (d->weight) : F_8_0;
 }
 
 /* Sets the weighting variable of D to V, or turning off
@@ -1410,9 +1448,8 @@ dict_compact_values (struct dictionary *d)
 
 /* Returns the number of values occupied by the variables in
    dictionary D.  All variables are considered if EXCLUDE_CLASSES
-   is 0, or it may contain one or more of (1u << DC_ORDINARY),
-   (1u << DC_SYSTEM), or (1u << DC_SCRATCH) to exclude the
-   corresponding type of variable.
+   is 0, or it may contain one or more of DC_ORDINARY, DC_SYSTEM,
+   or DC_SCRATCH to exclude the corresponding type of variable.
 
    The return value may be less than the number of values in one
    of dictionary D's cases (as returned by
@@ -1421,15 +1458,13 @@ dict_compact_values (struct dictionary *d)
 size_t
 dict_count_values (const struct dictionary *d, unsigned int exclude_classes)
 {
-  assert ((exclude_classes & ~((1u << DC_ORDINARY)
-                               | (1u << DC_SYSTEM)
-                               | (1u << DC_SCRATCH))) == 0);
+  assert (!(exclude_classes & ~DC_ALL));
 
   size_t n = 0;
   for (size_t i = 0; i < d->n_vars; i++)
     {
       enum dict_class class = var_get_dict_class (d->vars[i].var);
-      if (!(exclude_classes & (1u << class)))
+      if (!(exclude_classes & class))
         n++;
     }
   return n;
@@ -1449,15 +1484,13 @@ dict_get_compacted_proto (const struct dictionary *d,
   struct caseproto *proto;
   size_t i;
 
-  assert ((exclude_classes & ~((1u << DC_ORDINARY)
-                               | (1u << DC_SYSTEM)
-                               | (1u << DC_SCRATCH))) == 0);
+  assert (!(exclude_classes & ~DC_ALL));
 
   proto = caseproto_create ();
   for (i = 0; i < d->n_vars; i++)
     {
       struct variable *v = d->vars[i].var;
-      if (!(exclude_classes & (1u << var_get_dict_class (v))))
+      if (!(exclude_classes & var_get_dict_class (v)))
         proto = caseproto_add_width (proto, var_get_width (v));
     }
   return proto;
@@ -1785,6 +1818,97 @@ dict_unset_mrset_var (struct dictionary *dict, struct variable *var)
     }
 }
 \f
+
+/* Returns the variable set in DICT with index IDX, which must be between 0 and
+   the count returned by dict_get_n_varsets(), exclusive. */
+const struct varset *
+dict_get_varset (const struct dictionary *dict, size_t idx)
+{
+  assert (idx < dict->n_varsets);
+  return dict->varsets[idx];
+}
+
+/* Returns the number of variable sets in DICT. */
+size_t
+dict_get_n_varsets (const struct dictionary *dict)
+{
+  return dict->n_varsets;
+}
+
+/* Looks for a variable set named NAME in DICT.  If it finds one, returns its
+   index; otherwise, returns SIZE_MAX. */
+static size_t
+dict_lookup_varset_idx (const struct dictionary *dict, const char *name)
+{
+  for (size_t i = 0; i < dict->n_varsets; i++)
+    if (!utf8_strcasecmp (name, dict->varsets[i]->name))
+      return i;
+
+  return SIZE_MAX;
+}
+
+/* Looks for a multiple response set named NAME in DICT.  If it finds one,
+   returns it; otherwise, returns NULL. */
+const struct varset *
+dict_lookup_varset (const struct dictionary *dict, const char *name)
+{
+  size_t idx = dict_lookup_varset_idx (dict, name);
+  return idx != SIZE_MAX ? dict->varsets[idx] : NULL;
+}
+
+/* Adds VARSET to DICT, replacing any existing set with the same name.  Returns
+   true if a set was replaced, false if none existed with the specified name.
+
+   Ownership of VARSET is transferred to DICT. */
+bool
+dict_add_varset (struct dictionary *dict, struct varset *varset)
+{
+  size_t idx = dict_lookup_varset_idx (dict, varset->name);
+  if (idx == SIZE_MAX)
+    {
+      dict->varsets = xrealloc (dict->varsets,
+                               (dict->n_varsets + 1) * sizeof *dict->varsets);
+      dict->varsets[dict->n_varsets++] = varset;
+      return true;
+    }
+  else
+    {
+      varset_destroy (dict->varsets[idx]);
+      dict->varsets[idx] = varset;
+      return false;
+    }
+}
+
+/* Deletes all variable sets from DICT. */
+void
+dict_clear_varsets (struct dictionary *dict)
+{
+  for (size_t i = 0; i < dict->n_varsets; i++)
+    varset_destroy (dict->varsets[i]);
+  free (dict->varsets);
+  dict->varsets = NULL;
+  dict->n_varsets = 0;
+}
+
+/* Removes VAR, which must be in DICT, from DICT's multiple response sets. */
+static void
+dict_unset_varset_var (struct dictionary *dict, struct variable *var)
+{
+  assert (dict_contains_var (dict, var));
+
+  for (size_t i = 0; i < dict->n_varsets; i++)
+    {
+      struct varset *varset = dict->varsets[i];
+
+      for (size_t j = 0; j < varset->n_vars;)
+        if (varset->vars[j] == var)
+          remove_element (varset->vars, varset->n_vars--,
+                          sizeof *varset->vars, j);
+        else
+          j++;
+    }
+}
+\f
 /* Returns D's attribute set.  The caller may examine or modify
    the attribute set, but must not destroy it.  Destroying D or
    calling dict_set_attributes for D will also destroy D's