work on lexer
[pspp] / src / data / dictionary.c
index 054445a5e44fb45a108cf0a840aef1b8eae91183..5c8d6e8dc313f156c664db1417b9b4cf9f7a398a 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"
@@ -64,9 +65,9 @@ struct dictionary
     struct caseproto *proto;    /* Prototype for dictionary cases
                                    (updated lazily). */
     struct hmap name_map;      /* Variable index by name. */
-    int next_value_idx;         /* Index of next `union value' to allocate. */
     const struct variable **split;    /* SPLIT FILE vars. */
     size_t n_splits;            /* SPLIT FILE count. */
+    enum split_type split_type;
     struct variable *weight;    /* WEIGHT variable. */
     struct variable *filter;    /* FILTER variable. */
     casenumber case_limit;      /* Current case limit (N command). */
@@ -77,6 +78,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
@@ -95,6 +98,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. */
@@ -139,13 +143,6 @@ reindex_var (struct dictionary *d, struct vardict_info *vardict, bool skip_callb
     }
 }
 
-/* Sets the case_index in V's vardict to CASE_INDEX. */
-static void
-set_var_case_index (struct variable *v, int case_index)
-{
-  var_get_vardict (v)->case_index = case_index;
-}
-
 /* Removes the dictionary variables with indexes from FROM to TO (exclusive)
    from name_map. */
 static void
@@ -178,15 +175,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
@@ -212,16 +229,8 @@ invalidate_proto (struct dictionary *d)
 void
 dict_dump (const struct dictionary *d)
 {
-  int i;
-  for (i = 0 ; i < d->n_vars ; ++i)
-    {
-      const struct variable *v = d->vars[i].var;
-      printf ("Name: %s;\tdict_idx: %zu; case_idx: %zu\n",
-             var_get_name (v),
-             var_get_dict_index (v),
-             var_get_case_index (v));
-
-    }
+  for (size_t i = 0; i < d->n_vars; ++i)
+    printf ("%zu: %s\n", i, var_get_name (d->vars[i].var));
 }
 
 /* Associate CALLBACKS with DICT.  Callbacks will be invoked whenever
@@ -251,13 +260,16 @@ dict_copy_callbacks (struct dictionary *dest,
 struct dictionary *
 dict_create (const char *encoding)
 {
-  struct dictionary *d = XZALLOC (struct dictionary);
+  struct dictionary *d = xmalloc (sizeof *d);
 
-  d->encoding = xstrdup (encoding);
-  d->names_must_be_ids = true;
-  hmap_init (&d->name_map);
-  attrset_init (&d->attributes);
-  d->ref_cnt = 1;
+  *d = (struct dictionary) {
+    .encoding = xstrdup (encoding),
+    .names_must_be_ids = true,
+    .name_map = HMAP_INITIALIZER (d->name_map),
+    .attributes = ATTRSET_INITIALIZER (d->attributes),
+    .split_type = SPLIT_NONE,
+    .ref_cnt = 1,
+  };
 
   return d;
 }
@@ -265,43 +277,30 @@ dict_create (const char *encoding)
 /* Creates and returns a (deep) copy of an existing
    dictionary.
 
-   The new dictionary's case indexes are copied from the old
-   dictionary.  If the new dictionary won't be used to access
-   cases produced with the old dictionary, then the new
-   dictionary's case indexes should be compacted with
-   dict_compact_values to save space.
-
    Callbacks are not cloned. */
 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));
 
-      var_get_vardict (dv)->case_index = var_get_vardict (sv)->case_index;
+      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));
     }
 
-  d->next_value_idx = s->next_value_idx;
-
   d->n_splits = s->n_splits;
   if (d->n_splits > 0)
     {
        d->split = xnmalloc (d->n_splits, sizeof *d->split);
-      for (i = 0; i < d->n_splits; i++)
-        d->split[i] = dict_lookup_var_assert (d, var_get_name (s->split[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;
 
   if (s->weight != NULL)
     dict_set_weight (d, dict_lookup_var_assert (d, var_get_name (s->weight)));
@@ -315,12 +314,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;
@@ -334,10 +333,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
@@ -379,27 +388,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,
-                       bool skip_callbacks)
+                       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 = (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)
@@ -407,14 +422,26 @@ dict_set_split_vars__ (struct dictionary *d,
     }
 }
 
+enum split_type
+dict_get_split_type (const struct dictionary *d)
+{
+  return d->split_type;
+}
+
 /* Sets N split vars SPLIT in dictionary D. */
 void
 dict_set_split_vars (struct dictionary *d,
-                     struct variable *const *split, size_t n)
+                     struct variable *const *split, size_t n,
+                     enum split_type type)
 {
-  dict_set_split_vars__ (d, split, n, false);
+  dict_set_split_vars__ (d, split, n, type, false);
 }
 
+void
+dict_clear_split_vars (struct dictionary *d)
+{
+  dict_set_split_vars (d, NULL, 0, SPLIT_NONE);
+}
 \f
 
 /* Deletes variable V from dictionary D and frees V.
@@ -434,12 +461,12 @@ static void
 dict_delete_var__ (struct dictionary *d, struct variable *v, bool skip_callbacks)
 {
   int dict_index = var_get_dict_index (v);
-  const int case_index = var_get_case_index (v);
 
   assert (dict_contains_var (d, v));
 
   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);
@@ -463,8 +490,8 @@ dict_delete_var__ (struct dictionary *d, struct variable *v, bool skip_callbacks
   if (! skip_callbacks)
     {
       if (d->changed) d->changed (d, d->changed_data);
-      if (d->callbacks &&  d->callbacks->var_deleted)
-        d->callbacks->var_deleted (d, v, dict_index, case_index, d->cb_data);
+      if (d->callbacks &&  d->callbacks->vars_deleted)
+        d->callbacks->vars_deleted (d, dict_index, 1, d->cb_data);
     }
 
   invalidate_proto (d);
@@ -488,6 +515,7 @@ void
 dict_delete_var (struct dictionary *d, struct variable *v)
 {
   dict_delete_var__ (d, v, false);
+  invalidate_proto (d);
 }
 
 
@@ -503,6 +531,7 @@ dict_delete_vars (struct dictionary *d,
 
   while (count-- > 0)
     dict_delete_var (d, *vars++);
+  invalidate_proto (d);
 }
 
 /* Deletes the COUNT variables in D starting at index IDX.  This
@@ -519,33 +548,22 @@ dict_delete_consecutive_vars (struct dictionary *d, size_t idx, size_t count)
 {
   assert (idx + count <= d->n_vars);
 
-  /* We need to store the variable and the corresponding case_index
-     for the delete callbacks later. We store them in a linked list.*/
-  struct delvar {
-    struct ll ll;
-    struct variable *var;
-    int case_index;
-  };
-  struct ll_list list = LL_INITIALIZER (list);
+  struct variable **vars = xnmalloc (count, sizeof *vars);
 
-  for (size_t i = idx; i < idx + count; i++)
+  for (size_t i = 0; i < count; i++)
     {
-      struct delvar *dv = xmalloc (sizeof (struct delvar));
-      assert (dv);
-      struct variable *v = d->vars[i].var;
+      struct variable *v = d->vars[idx + i].var;
+      vars[i] = v;
 
       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);
 
       if (d->filter == v)
        dict_set_filter (d, NULL);
-
-      dv->var = v;
-      dv->case_index = var_get_case_index (v);
-      ll_push_tail (&list, (struct ll *)dv);
     }
 
   dict_clear_vectors (d);
@@ -563,17 +581,17 @@ dict_delete_consecutive_vars (struct dictionary *d, size_t idx, size_t count)
 
   /* Now issue the variable delete callbacks and delete
      the variables. The vardict is not valid at this point
-     anymore. That is the reason why we stored the
-     caseindex before reindexing. */
-  for (size_t vi = idx; vi < idx + count; vi++)
+     anymore. */
+  if (d->callbacks &&  d->callbacks->vars_deleted)
+    d->callbacks->vars_deleted (d, idx, count, d->cb_data);
+  for (size_t i = 0; i < count; i++)
     {
-      struct delvar *dv = (struct delvar *) ll_pop_head (&list);
-      var_clear_vardict (dv->var);
-      if (d->callbacks &&  d->callbacks->var_deleted)
-        d->callbacks->var_deleted (d, dv->var, vi, dv->case_index, d->cb_data);
-      var_unref (dv->var);
-      free (dv);
+      var_clear_vardict (vars[i]);
+      var_unref (vars[i]);
     }
+  free (vars);
+
+  invalidate_proto (d);
 }
 
 /* Deletes scratch variables from dictionary D. */
@@ -589,6 +607,8 @@ dict_delete_scratch_vars (struct dictionary *d)
       dict_delete_var (d, d->vars[i].var);
     else
       i++;
+
+  invalidate_proto (d);
 }
 
 \f
@@ -610,8 +630,7 @@ dict_clear__ (struct dictionary *d, bool skip_callbacks)
   d->n_vars = d->allocated_vars = 0;
   invalidate_proto (d);
   hmap_clear (&d->name_map);
-  d->next_value_idx = 0;
-  dict_set_split_vars__ (d, NULL, 0, skip_callbacks);
+  dict_set_split_vars__ (d, NULL, 0, SPLIT_NONE, skip_callbacks);
 
   if (skip_callbacks)
     {
@@ -652,6 +671,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);
 }
@@ -738,13 +758,8 @@ dict_get_vars_mutable (const struct dictionary *d, struct variable ***vars,
 }
 
 static struct variable *
-add_var_with_case_index (struct dictionary *d, struct variable *v,
-                         int case_index)
+add_var (struct dictionary *d, struct variable *v)
 {
-  struct vardict_info *vardict;
-
-  assert (case_index >= d->next_value_idx);
-
   /* Update dictionary. */
   if (d->n_vars >= d->allocated_vars)
     {
@@ -760,12 +775,13 @@ add_var_with_case_index (struct dictionary *d, struct variable *v,
         }
     }
 
-  vardict = &d->vars[d->n_vars++];
-  vardict->dict = d;
-  vardict->var = v;
+  struct vardict_info *vardict = &d->vars[d->n_vars++];
+  *vardict = (struct vardict_info) {
+    .dict = d,
+    .var = v,
+  };
   hmap_insert (&d->name_map, &vardict->name_node,
                utf8_hash_case_string (var_get_name (v), 0));
-  vardict->case_index = case_index;
   var_set_vardict (v, vardict);
 
   if (d->changed) d->changed (d, d->changed_data);
@@ -773,17 +789,10 @@ add_var_with_case_index (struct dictionary *d, struct variable *v,
     d->callbacks->var_added (d, var_get_dict_index (v), d->cb_data);
 
   invalidate_proto (d);
-  d->next_value_idx = case_index + 1;
 
   return v;
 }
 
-static struct variable *
-add_var (struct dictionary *d, struct variable *v)
-{
-  return add_var_with_case_index (d, v, d->next_value_idx);
-}
-
 /* Creates and returns a new variable in D with the given NAME
    and WIDTH.  Returns a null pointer if the given NAME would
    duplicate that of an existing variable in the dictionary. */
@@ -852,15 +861,6 @@ dict_clone_var_as_assert (struct dictionary *d, const struct variable *old_var,
   return add_var (d, new_var);
 }
 
-struct variable *
-dict_clone_var_in_place_assert (struct dictionary *d,
-                                const struct variable *old_var)
-{
-  assert (dict_lookup_var (d, var_get_name (old_var)) == NULL);
-  return add_var_with_case_index (d, var_clone (old_var),
-                                  var_get_case_index (old_var));
-}
-
 /* Returns the variable named NAME in D, or a null pointer if no
    variable has that name. */
 struct variable *
@@ -904,13 +904,18 @@ dict_contains_var (const struct dictionary *d, const struct variable *v)
 void
 dict_reorder_var (struct dictionary *d, struct variable *v, size_t new_index)
 {
-  size_t old_index = var_get_dict_index (v);
-
   assert (new_index < d->n_vars);
 
+  size_t old_index = var_get_dict_index (v);
+  if (new_index == old_index)
+    return;
+
   unindex_vars (d, MIN (old_index, new_index), MAX (old_index, new_index) + 1);
   move_element (d->vars, d->n_vars, sizeof *d->vars, old_index, new_index);
   reindex_vars (d, MIN (old_index, new_index), MAX (old_index, new_index) + 1, false);
+
+  if (d->callbacks && d->callbacks->var_moved)
+    d->callbacks->var_moved (d, new_index, old_index, d->cb_data);
 }
 
 /* Reorders the variables in D, placing the COUNT variables
@@ -1241,7 +1246,7 @@ dict_get_weight (const struct dictionary *d)
 }
 
 /* Returns the value of D's weighting variable in case C, except
-   that a negative weight is returned as 0.  Returns 1 if the
+   that a negative or missing weight is returned as 0.  Returns 1 if the
    dictionary is unweighted.  Will warn about missing, negative,
    or zero values if *WARN_ON_INVALID is true.  The function will
    set *WARN_ON_INVALID to false if an invalid weight is
@@ -1262,11 +1267,20 @@ dict_get_case_weight (const struct dictionary *d, const struct ccase *c,
     }
 }
 
+/* Like dict_get_case_weight(), but additionally rounds each weight to the
+   nearest integer.  */
+double
+dict_get_rounded_case_weight (const struct dictionary *d,
+                              const struct ccase *c, bool *warn_on_invalid)
+{
+  return floor (dict_get_case_weight (d, c, warn_on_invalid) + 0.5);
+}
+
 /* 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
@@ -1336,105 +1350,33 @@ dict_get_proto (const struct dictionary *d_)
   struct dictionary *d = CONST_CAST (struct dictionary *, d_);
   if (d->proto == NULL)
     {
-      size_t i;
-
-      d->proto = caseproto_create ();
-      d->proto = caseproto_reserve (d->proto, d->n_vars);
-      for (i = 0; i < d->n_vars; i++)
-        d->proto = caseproto_set_width (d->proto,
-                                        var_get_case_index (d->vars[i].var),
-                                        var_get_width (d->vars[i].var));
+      short int *widths = xnmalloc (d->n_vars, sizeof *widths);
+      for (size_t i = 0; i < d->n_vars; i++)
+        widths[i] = var_get_width (d->vars[i].var);
+      d->proto = caseproto_from_widths (widths, d->n_vars);
     }
   return d->proto;
 }
 
-/* Returns the case index of the next value to be added to D.
-   This value is the number of `union value's that need to be
-   allocated to store a case for dictionary D. */
-int
-dict_get_next_value_idx (const struct dictionary *d)
-{
-  return d->next_value_idx;
-}
-
-/* Returns the number of bytes needed to store a case for
-   dictionary D. */
-size_t
-dict_get_case_size (const struct dictionary *d)
-{
-  return sizeof (union value) * dict_get_next_value_idx (d);
-}
-
-/* Reassigns values in dictionary D so that fragmentation is
-   eliminated. */
-void
-dict_compact_values (struct dictionary *d)
-{
-  size_t i;
-
-  d->next_value_idx = 0;
-  for (i = 0; i < d->n_vars; i++)
-    {
-      struct variable *v = d->vars[i].var;
-      set_var_case_index (v, d->next_value_idx++);
-    }
-  invalidate_proto (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.
-
-   The return value may be less than the number of values in one
-   of dictionary D's cases (as returned by
-   dict_get_next_value_idx) even if E is 0, because there may be
-   gaps in D's cases due to deleted variables. */
+   is 0, or it may contain one or more of DC_ORDINARY, DC_SYSTEM,
+   or DC_SCRATCH to exclude the corresponding type of variable. */
 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;
 }
 
-/* Returns the case prototype that would result after deleting
-   all variables from D that are not in one of the
-   EXCLUDE_CLASSES and compacting the dictionary with
-   dict_compact().
-
-   The caller must unref the returned caseproto when it is no
-   longer needed. */
-struct caseproto *
-dict_get_compacted_proto (const struct dictionary *d,
-                          unsigned int exclude_classes)
-{
-  struct caseproto *proto;
-  size_t i;
-
-  assert ((exclude_classes & ~((1u << DC_ORDINARY)
-                               | (1u << DC_SYSTEM)
-                               | (1u << DC_SCRATCH))) == 0);
-
-  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))))
-        proto = caseproto_add_width (proto, var_get_width (v));
-    }
-  return proto;
-}
 /* Returns the file label for D, or a null pointer if D is
    unlabeled (see cmd_file_label()). */
 const char *
@@ -1525,7 +1467,7 @@ dict_add_document_line (struct dictionary *d, const char *line,
   truncated = line[trunc_len] != '\0';
   if (truncated && issue_warning)
     {
-      /* Note to translators: "bytes" is correct, not characters */
+      /* TRANSLATORS: "bytes" is correct, not characters due to UTF encoding */
       msg (SW, _("Truncating document line to %d bytes."), DOC_LINE_LENGTH);
     }
 
@@ -1758,6 +1700,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
@@ -1810,55 +1843,6 @@ dict_var_changed (const struct variable *v, unsigned int what, struct variable *
 }
 
 
-\f
-/* Dictionary used to contain "internal variables". */
-static struct dictionary *internal_dict;
-
-/* Create a variable of the specified WIDTH to be used for internal
-   calculations only.  The variable is assigned case index CASE_IDX. */
-struct variable *
-dict_create_internal_var (int case_idx, int width)
-{
-  if (internal_dict == NULL)
-    internal_dict = dict_create ("UTF-8");
-
-  for (;;)
-    {
-      static int counter = INT_MAX / 2;
-      struct variable *var;
-      char name[64];
-
-      if (++counter == INT_MAX)
-        counter = INT_MAX / 2;
-
-      sprintf (name, "$internal%d", counter);
-      var = dict_create_var (internal_dict, name, width);
-      if (var != NULL)
-        {
-          set_var_case_index (var, case_idx);
-          return var;
-        }
-    }
-}
-
-/* Destroys VAR, which must have been created with
-   dict_create_internal_var(). */
-void
-dict_destroy_internal_var (struct variable *var)
-{
-  if (var != NULL)
-    {
-      dict_delete_var (internal_dict, var);
-
-      /* Destroy internal_dict if it has no variables left, just so that
-         valgrind --leak-check --show-reachable won't show internal_dict. */
-      if (dict_get_n_vars (internal_dict) == 0)
-        {
-          dict_unref (internal_dict);
-          internal_dict = NULL;
-        }
-    }
-}
 \f
 int
 vardict_get_dict_index (const struct vardict_info *vardict)