+
+\f
+/* Returns the SPLIT FILE vars (see cmd_split_file()). Call
+ dict_get_n_splits() to determine how many SPLIT FILE vars
+ there are. Returns a null pointer if and only if there are no
+ SPLIT FILE vars. */
+const struct variable *const *
+dict_get_split_vars (const struct dictionary *d)
+{
+ return d->split;
+}
+
+/* Returns the number of SPLIT FILE vars. */
+size_t
+dict_get_n_splits (const struct dictionary *d)
+{
+ return d->n_splits;
+}
+
+/* Removes variable V, which must be in D, from D's set of split
+ variables. */
+static void
+dict_unset_split_var (struct dictionary *d, struct variable *v, bool skip_callbacks)
+{
+ int orig_count;
+
+ assert (dict_contains_var (d, v));
+
+ orig_count = d->n_splits;
+ d->n_splits = remove_equal (d->split, d->n_splits, sizeof *d->split,
+ &v, compare_var_ptrs, NULL);
+ if (orig_count != d->n_splits && !skip_callbacks)
+ {
+ if (d->changed) d->changed (d, d->changed_data);
+ /* We changed the set of split variables so invoke the
+ callback. */
+ if (d->callbacks && d->callbacks->split_changed)
+ d->callbacks->split_changed (d, d->cb_data);
+ }
+}
+
+
+/* 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;
+ if (n > 0)
+ {
+ d->split = xnrealloc (d->split, n, sizeof *d->split) ;
+ memcpy (d->split, split, n * sizeof *d->split);
+ }
+ else
+ {
+ free (d->split);
+ d->split = NULL;
+ }
+
+ if (!skip_callbacks)
+ {
+ if (d->changed) d->changed (d, d->changed_data);
+ if (d->callbacks && d->callbacks->split_changed)
+ d->callbacks->split_changed (d, d->cb_data);
+ }
+}
+
+/* Sets N split vars SPLIT in dictionary D. */
+void
+dict_set_split_vars (struct dictionary *d,
+ struct variable *const *split, size_t n,
+ enum split_type type)
+{
+ 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.
+
+ This is a very bad idea if there might be any pointers to V
+ from outside D. In general, no variable in the active dataset's
+ dictionary should be deleted when any transformations are
+ active on the dictionary's dataset, because those
+ transformations might reference the deleted variable. The
+ safest time to delete a variable is just after a procedure has
+ been executed, as done by DELETE VARIABLES.
+
+ Pointers to V within D are not a problem, because
+ dict_delete_var() knows to remove V from split variables,
+ weights, filters, etc. */
+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);
+
+ if (d->weight == v)
+ dict_set_weight (d, NULL);
+
+ if (d->filter == v)
+ dict_set_filter (d, NULL);
+
+ dict_clear_vectors (d);
+
+ /* Remove V from var array. */
+ unindex_vars (d, dict_index, d->n_vars);
+ remove_element (d->vars, d->n_vars, sizeof *d->vars, dict_index);
+ d->n_vars--;
+
+ /* Update dict_index for each affected variable. */
+ reindex_vars (d, dict_index, d->n_vars, skip_callbacks);
+
+ /* Free memory. */
+ var_clear_vardict (v);
+
+ 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);
+ }
+
+ invalidate_proto (d);
+ var_unref (v);
+}
+
+/* Deletes variable V from dictionary D and frees V.
+
+ This is a very bad idea if there might be any pointers to V
+ from outside D. In general, no variable in the active dataset's
+ dictionary should be deleted when any transformations are
+ active on the dictionary's dataset, because those
+ transformations might reference the deleted variable. The
+ safest time to delete a variable is just after a procedure has
+ been executed, as done by DELETE VARIABLES.
+
+ Pointers to V within D are not a problem, because
+ dict_delete_var() knows to remove V from split variables,
+ weights, filters, etc. */
+void
+dict_delete_var (struct dictionary *d, struct variable *v)
+{
+ dict_delete_var__ (d, v, false);
+}
+
+
+/* Deletes the COUNT variables listed in VARS from D. This is
+ unsafe; see the comment on dict_delete_var() for details. */
+void
+dict_delete_vars (struct dictionary *d,
+ struct variable *const *vars, size_t count)
+{
+ /* FIXME: this can be done in O(count) time, but this algorithm
+ is O(count**2). */
+ assert (count == 0 || vars != NULL);
+
+ while (count-- > 0)
+ dict_delete_var (d, *vars++);
+}
+
+/* Deletes the COUNT variables in D starting at index IDX. This
+ is unsafe; see the comment on dict_delete_var() for
+ details. Deleting consecutive vars will result in less callbacks
+ compared to iterating over dict_delete_var.
+ A simple while loop over dict_delete_var will
+ produce (d->n_vars - IDX) * COUNT variable changed callbacks
+ plus COUNT variable delete callbacks.
+ This here produces d->n_vars - IDX variable changed callbacks
+ plus COUNT variable delete callbacks. */
+void
+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);
+
+ for (size_t i = idx; i < idx + count; i++)
+ {
+ struct delvar *dv = xmalloc (sizeof (struct delvar));
+ assert (dv);
+ struct variable *v = d->vars[i].var;
+
+ dict_unset_split_var (d, v, false);
+ dict_unset_mrset_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);
+
+ /* Remove variables from var array. */
+ unindex_vars (d, idx, d->n_vars);
+ remove_range (d->vars, d->n_vars, sizeof *d->vars, idx, count);
+ d->n_vars -= count;
+
+ /* Reindexing will result variable-changed callback */
+ reindex_vars (d, idx, d->n_vars, false);
+
+ invalidate_proto (d);
+ if (d->changed) d->changed (d, d->changed_data);
+
+ /* 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++)
+ {
+ 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);
+ }
+}
+
+/* Deletes scratch variables from dictionary D. */
+void
+dict_delete_scratch_vars (struct dictionary *d)
+{
+ int i;
+
+ /* FIXME: this can be done in O(count) time, but this algorithm
+ is O(count**2). */
+ for (i = 0; i < d->n_vars;)
+ if (var_get_dict_class (d->vars[i].var) == DC_SCRATCH)
+ dict_delete_var (d, d->vars[i].var);
+ else
+ i++;
+}
+
+\f
+