1 /* PSPP - computes sample statistics.
2 Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3 Written by Ben Pfaff <blp@gnu.org>.
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License as
7 published by the Free Software Foundation; either version 2 of the
8 License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
28 #include <unistd.h> /* Required by SunOS4. */
41 #include "value-labels.h"
44 Virtual File Manager (vfm):
46 vfm is used to process data files. It uses the model that data is
47 read from one stream (the data source), then written to another
48 (the data sink). The data source is then deleted and the data sink
49 becomes the data source for the next procedure. */
51 #include "debug-print.h"
53 /* Procedure execution data. */
54 struct write_case_data
56 void (*beginfunc) (void *);
57 int (*procfunc) (struct ccase *, void *);
58 void (*endfunc) (void *);
62 /* This is used to read from the active file. */
63 struct case_stream *vfm_source;
65 /* This is used to write to the replacement active file. */
66 struct case_stream *vfm_sink;
68 /* Information about the data source. */
69 struct stream_info vfm_source_info;
71 /* Information about the data sink. */
72 struct stream_info vfm_sink_info;
74 /* Nonzero if the case needs to have values deleted before being
75 stored, zero otherwise. */
76 int compaction_necessary;
78 /* Number of values after compaction, or the same as
79 vfm_sink_info.nval, if compaction is not necessary. */
82 /* Temporary case buffer with enough room for `compaction_nval'
84 struct ccase *compaction_case;
86 /* Within a session, when paging is turned on, it is never turned back
87 off. This policy might be too aggressive. */
88 static int paging = 0;
90 /* Time at which vfm was last invoked. */
91 time_t last_vfm_invocation;
93 /* Number of cases passed to proc_func(). */
94 static int case_count;
97 int n_lag; /* Number of cases to lag. */
98 static int lag_count; /* Number of cases in lag_queue so far. */
99 static int lag_head; /* Index where next case will be added. */
100 static struct ccase **lag_queue; /* Array of n_lag ccase * elements. */
102 static void open_active_file (void);
103 static void close_active_file (struct write_case_data *);
104 static int SPLIT_FILE_procfunc (struct ccase *, void *);
105 static void finish_compaction (void);
106 static void lag_case (void);
107 static int procedure_write_case (struct write_case_data *);
108 static void clear_temp_case (void);
109 static int exclude_this_case (void);
111 /* Public functions. */
113 /* Reads all the cases from the active file, transforms them by
114 the active set of transformations, calls PROCFUNC with CURCASE
115 set to the case, and writes them to a new active file.
117 Divides the active file into zero or more series of one or more
118 cases each. BEGINFUNC is called before each series. ENDFUNC is
119 called after each series.
121 Arbitrary user-specified data AUX is passed to BEGINFUNC,
122 PROCFUNC, and ENDFUNC as auxiliary data. */
124 procedure (void (*beginfunc) (void *),
125 int (*procfunc) (struct ccase *curcase, void *),
126 void (*endfunc) (void *),
129 struct write_case_data procedure_write_data;
130 struct write_case_data split_file_data;
132 if (dict_get_split_cnt (default_dict) == 0)
134 /* Normally we just use the data passed by the user. */
135 procedure_write_data.beginfunc = beginfunc;
136 procedure_write_data.procfunc = procfunc;
137 procedure_write_data.endfunc = endfunc;
138 procedure_write_data.aux = aux;
142 /* Under SPLIT FILE, we add a layer of indirection. */
143 procedure_write_data.beginfunc = NULL;
144 procedure_write_data.procfunc = SPLIT_FILE_procfunc;
145 procedure_write_data.endfunc = endfunc;
146 procedure_write_data.aux = &split_file_data;
148 split_file_data.beginfunc = beginfunc;
149 split_file_data.procfunc = procfunc;
150 split_file_data.endfunc = endfunc;
151 split_file_data.aux = aux;
154 last_vfm_invocation = time (NULL);
157 vfm_source->read (procedure_write_case, &procedure_write_data);
158 close_active_file (&procedure_write_data);
161 /* Active file processing support. Subtly different semantics from
164 static int process_active_file_write_case (struct write_case_data *data);
166 /* The casefunc might want us to stop calling it. */
167 static int not_canceled;
169 /* Reads all the cases from the active file and passes them one-by-one
170 to CASEFUNC in temp_case. Before any cases are passed, calls
171 BEGINFUNC. After all the cases have been passed, calls ENDFUNC.
172 BEGINFUNC, CASEFUNC, and ENDFUNC can write temp_case to the output
173 file by calling process_active_file_output_case().
175 process_active_file() ignores TEMPORARY, SPLIT FILE, and N. */
177 process_active_file (void (*beginfunc) (void *),
178 int (*casefunc) (struct ccase *curcase, void *),
179 void (*endfunc) (void *),
182 struct write_case_data process_active_write_data;
184 process_active_write_data.beginfunc = beginfunc;
185 process_active_write_data.procfunc = casefunc;
186 process_active_write_data.endfunc = endfunc;
187 process_active_write_data.aux = aux;
194 /* There doesn't necessarily need to be an active file. */
196 vfm_source->read (process_active_file_write_case,
197 &process_active_write_data);
200 close_active_file (&process_active_write_data);
203 /* Pass the current case to casefunc. */
205 process_active_file_write_case (struct write_case_data *data)
207 /* Index of current transformation. */
210 for (cur_trns = f_trns ; cur_trns != temp_trns; )
214 code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case);
218 /* Next transformation. */
222 /* Delete this case. */
225 /* Go to that transformation. */
234 /* Call the procedure if FILTER and PROCESS IF don't prohibit it. */
235 if (not_canceled && !exclude_this_case ())
236 not_canceled = data->procfunc (temp_case, data->aux);
246 /* Write temp_case to the active file. */
248 process_active_file_output_case (void)
250 vfm_sink_info.ncases++;
254 /* Opening the active file. */
256 /* It might be usefully noted that the following several functions are
257 given in the order that they are called by open_active_file(). */
259 /* Prepare to write to the replacement active file. */
261 prepare_for_writing (void)
263 /* FIXME: If ALL the conditions listed below hold true, then the
264 replacement active file is guaranteed to be identical to the
265 original active file:
267 1. TEMPORARY was the first transformation, OR, there were no
268 transformations at all.
270 2. Input is not coming from an input program.
272 3. Compaction is not necessary.
274 So, in this case, we shouldn't have to replace the active
275 file--it's just a waste of time and space. */
277 vfm_sink_info.ncases = 0;
278 vfm_sink_info.nval = dict_get_next_value_idx (default_dict);
279 vfm_sink_info.case_size = dict_get_case_size (default_dict);
281 if (vfm_sink == NULL)
283 if (vfm_sink_info.case_size * vfm_source_info.ncases > MAX_WORKSPACE
286 msg (MW, _("Workspace overflow predicted. Max workspace is "
287 "currently set to %d KB (%d cases at %d bytes each). "
288 "Paging active file to disk."),
289 MAX_WORKSPACE / 1024, MAX_WORKSPACE / vfm_sink_info.case_size,
290 vfm_sink_info.case_size);
295 vfm_sink = paging ? &vfm_disk_stream : &vfm_memory_stream;
299 /* Arrange for compacting the output cases for storage. */
301 arrange_compaction (void)
303 int count_values = 0;
308 /* Count up the number of `value's that will be output. */
309 for (i = 0; i < dict_get_var_cnt (temp_dict); i++)
311 struct variable *v = dict_get_var (temp_dict, i);
313 if (v->name[0] != '#')
316 count_values += v->nv;
319 assert (temporary == 2
320 || count_values <= dict_get_next_value_idx (temp_dict));
323 /* Compaction is only necessary if the number of `value's to output
324 differs from the number already present. */
325 compaction_nval = count_values;
326 if (temporary == 2 || count_values != dict_get_next_value_idx (temp_dict))
327 compaction_necessary = 1;
329 compaction_necessary = 0;
335 /* Prepares the temporary case and compaction case. */
337 make_temp_case (void)
339 temp_case = xmalloc (vfm_sink_info.case_size);
341 if (compaction_necessary)
342 compaction_case = xmalloc (sizeof (struct ccase)
343 + sizeof (union value) * (compaction_nval - 1));
347 /* Returns the name of the variable that owns the index CCASE_INDEX
350 index_to_varname (int ccase_index)
354 for (i = 0; i < default_dict.nvar; i++)
356 struct variable *v = default_dict.var[i];
358 if (ccase_index >= v->fv && ccase_index < v->fv + v->nv)
359 return default_dict.var[i]->name;
365 /* Initializes temp_case from the vectors that say which `value's
366 need to be initialized just once, and which ones need to be
367 re-initialized before every case. */
369 vector_initialization (void)
371 size_t var_cnt = dict_get_var_cnt (default_dict);
374 for (i = 0; i < var_cnt; i++)
376 struct variable *v = dict_get_var (default_dict, i);
378 if (v->type == NUMERIC)
381 temp_case->data[v->fv].f = 0.0;
383 temp_case->data[v->fv].f = SYSMIS;
386 memset (temp_case->data[v->fv].s, ' ', v->width);
390 /* Sets all the lag-related variables based on value of n_lag. */
401 lag_queue = xmalloc (n_lag * sizeof *lag_queue);
402 for (i = 0; i < n_lag; i++)
403 lag_queue[i] = xmalloc (dict_get_case_size (temp_dict));
406 /* There is a lot of potential confusion in the vfm and related
407 routines over the number of `value's at each stage of the process.
408 Here is each nval count, with explanation, as set up by
411 vfm_source_info.nval: Number of `value's in the cases returned by
412 the source stream. This value turns out not to be very useful, but
413 we maintain it anyway.
415 vfm_sink_info.nval: Number of `value's in the cases after all
416 transformations have been performed. Never less than
417 vfm_source_info.nval.
419 temp_dict->nval: Number of `value's in the cases after the
420 transformations leading up to TEMPORARY have been performed. If
421 TEMPORARY was not specified, this is equal to vfm_sink_info.nval.
422 Never less than vfm_sink_info.nval.
424 compaction_nval: Number of `value's in the cases after the
425 transformations leading up to TEMPORARY have been performed and the
426 case has been compacted by compact_case(), if compaction is
427 necessary. This the number of `value's in the cases saved by the
428 sink stream. (However, note that the cases passed to the sink
429 stream have not yet been compacted. It is the responsibility of
430 the data sink to call compact_case().) This may be less than,
431 greater than, or equal to vfm_source_info.nval. `compaction'
432 becomes the new value of default_dict.nval after the procedure is
435 default_dict.nval: This is often an alias for temp_dict->nval. As
436 such it can really have no separate existence until the procedure
437 is complete. For this reason it should *not* be referenced inside
438 the execution of a procedure. */
439 /* Makes all preparations for reading from the data source and writing
442 open_active_file (void)
444 /* Sometimes we want to refer to the dictionary that applies to the
445 data actually written to the sink. This is either temp_dict or
446 default_dict. However, if TEMPORARY is not on, then temp_dict
447 does not apply. So, we can set temp_dict to default_dict in this
452 temp_dict = default_dict;
455 /* No cases passed to the procedure yet. */
459 prepare_for_writing ();
460 arrange_compaction ();
462 vector_initialization ();
463 discard_ctl_stack ();
467 debug_printf (("vfm: reading from %s source, writing to %s sink.\n",
468 vfm_source->name, vfm_sink->name));
469 debug_printf (("vfm: vfm_source_info.nval=%d, vfm_sink_info.nval=%d, "
470 "temp_dict->nval=%d, compaction_nval=%d, "
471 "default_dict.nval=%d\n",
472 vfm_source_info.nval, vfm_sink_info.nval, temp_dict->nval,
473 compaction_nval, default_dict.nval));
476 /* Closes the active file. */
478 close_active_file (struct write_case_data *data)
480 /* Close the current case group. */
481 if (case_count && data->endfunc != NULL)
482 data->endfunc (data->aux);
484 /* Stop lagging (catch up?). */
489 for (i = 0; i < n_lag; i++)
495 /* Assume the dictionary from right before TEMPORARY, if any. Turn
499 dict_destroy (default_dict);
500 default_dict = temp_dict;
504 /* Finish compaction. */
505 if (compaction_necessary)
506 finish_compaction ();
508 /* Old data sink --> New data source. */
509 if (vfm_source && vfm_source->destroy_source)
510 vfm_source->destroy_source ();
512 vfm_source = vfm_sink;
513 vfm_source_info.ncases = vfm_sink_info.ncases;
514 vfm_source_info.nval = compaction_nval;
515 vfm_source_info.case_size = (sizeof (struct ccase)
516 + (compaction_nval - 1) * sizeof (union value));
517 if (vfm_source->mode)
520 /* Old data sink is gone now. */
523 /* Cancel TEMPORARY. */
526 /* Free temporary cases. */
530 free (compaction_case);
531 compaction_case = NULL;
533 /* Cancel PROCESS IF. */
534 expr_free (process_if_expr);
535 process_if_expr = NULL;
537 /* Cancel FILTER if temporary. */
538 if (dict_get_filter (default_dict) != NULL && !FILTER_before_TEMPORARY)
539 dict_set_filter (default_dict, NULL);
541 /* Cancel transformations. */
542 cancel_transformations ();
544 /* Turn off case limiter. */
545 dict_set_case_limit (default_dict, 0);
547 /* Clear VECTOR vectors. */
548 dict_clear_vectors (default_dict);
550 debug_printf (("vfm: procedure complete\n\n"));
553 /* Disk case stream. */
555 /* Associated files. */
556 FILE *disk_source_file;
557 FILE *disk_sink_file;
559 /* Initializes the disk sink. */
561 disk_stream_init (void)
563 disk_sink_file = tmpfile ();
566 msg (ME, _("An error occurred attempting to create a temporary "
567 "file for use as the active file: %s."),
573 /* Reads all cases from the disk source and passes them one by one to
576 disk_stream_read (write_case_func *write_case, write_case_data wc_data)
580 for (i = 0; i < vfm_source_info.ncases; i++)
582 if (!fread (temp_case, vfm_source_info.case_size, 1, disk_source_file))
584 msg (ME, _("An error occurred while attempting to read from "
585 "a temporary file created for the active file: %s."),
591 if (!write_case (wc_data))
596 /* Writes temp_case to the disk sink. */
598 disk_stream_write (void)
600 union value *src_case;
602 if (compaction_necessary)
604 compact_case (compaction_case, temp_case);
605 src_case = (union value *) compaction_case;
607 else src_case = (union value *) temp_case;
609 if (fwrite (src_case, sizeof *src_case * compaction_nval, 1,
610 disk_sink_file) != 1)
612 msg (ME, _("An error occurred while attempting to write to a "
613 "temporary file used as the active file: %s."),
619 /* Switches the stream from a sink to a source. */
621 disk_stream_mode (void)
623 /* Rewind the sink. */
624 if (fseek (disk_sink_file, 0, SEEK_SET) != 0)
626 msg (ME, _("An error occurred while attempting to rewind a "
627 "temporary file used as the active file: %s."),
632 /* Sink --> source variables. */
633 disk_source_file = disk_sink_file;
636 /* Destroys the source's internal data. */
638 disk_stream_destroy_source (void)
640 if (disk_source_file)
642 fclose (disk_source_file);
643 disk_source_file = NULL;
647 /* Destroys the sink's internal data. */
649 disk_stream_destroy_sink (void)
653 fclose (disk_sink_file);
654 disk_sink_file = NULL;
659 struct case_stream vfm_disk_stream =
665 disk_stream_destroy_source,
666 disk_stream_destroy_sink,
670 /* Memory case stream. */
672 /* List of cases stored in the stream. */
673 struct case_list *memory_source_cases;
674 struct case_list *memory_sink_cases;
677 struct case_list *memory_sink_iter;
679 /* Maximum number of cases. */
680 int memory_sink_max_cases;
682 /* Initializes the memory stream variables for writing. */
684 memory_stream_init (void)
686 memory_sink_cases = NULL;
687 memory_sink_iter = NULL;
689 assert (compaction_nval);
690 memory_sink_max_cases = MAX_WORKSPACE / (sizeof (union value) * compaction_nval);
693 /* Reads the case stream from memory and passes it to write_case(). */
695 memory_stream_read (write_case_func *write_case, write_case_data wc_data)
697 while (memory_source_cases != NULL)
699 memcpy (temp_case, &memory_source_cases->c, vfm_source_info.case_size);
702 struct case_list *current = memory_source_cases;
703 memory_source_cases = memory_source_cases->next;
707 if (!write_case (wc_data))
712 /* Writes temp_case to the memory stream. */
714 memory_stream_write (void)
716 struct case_list *new_case = malloc (sizeof (struct case_list)
717 + ((compaction_nval - 1)
718 * sizeof (union value)));
720 /* If we've got memory to spare then add it to the linked list. */
721 if (vfm_sink_info.ncases <= memory_sink_max_cases && new_case != NULL)
723 if (compaction_necessary)
724 compact_case (&new_case->c, temp_case);
726 memcpy (&new_case->c, temp_case, sizeof (union value) * compaction_nval);
728 /* Append case to linked list. */
729 if (memory_sink_cases)
730 memory_sink_iter = memory_sink_iter->next = new_case;
732 memory_sink_iter = memory_sink_cases = new_case;
736 /* Out of memory. Write the active file to disk. */
737 struct case_list *cur, *next;
739 /* Notify the user. */
741 msg (MW, _("Virtual memory exhausted. Paging active file "
744 msg (MW, _("Workspace limit of %d KB (%d cases at %d bytes each) "
745 "overflowed. Paging active file to disk."),
746 MAX_WORKSPACE / 1024, memory_sink_max_cases,
747 compaction_nval * sizeof (union value));
751 /* Switch to a disk sink. */
752 vfm_sink = &vfm_disk_stream;
756 /* Terminate the list. */
757 if (memory_sink_iter)
758 memory_sink_iter->next = NULL;
760 /* Write the cases to disk and destroy them. We can't call
761 vfm->sink->write() because of compaction. */
762 for (cur = memory_sink_cases; cur; cur = next)
765 if (fwrite (cur->c.data, sizeof (union value) * compaction_nval, 1,
766 disk_sink_file) != 1)
768 msg (ME, _("An error occurred while attempting to "
769 "write to a temporary file created as the "
770 "active file, while paging to disk: %s."),
777 /* Write the current case to disk. */
782 /* If the data is stored in memory, causes it to be written to disk.
783 To be called only *between* procedure()s, not within them. */
787 if (vfm_source == &vfm_memory_stream)
789 /* Switch to a disk sink. */
790 vfm_sink = &vfm_disk_stream;
794 /* Write the cases to disk and destroy them. We can't call
795 vfm->sink->write() because of compaction. */
797 struct case_list *cur, *next;
799 for (cur = memory_source_cases; cur; cur = next)
802 if (fwrite (cur->c.data, sizeof *cur->c.data * compaction_nval, 1,
803 disk_sink_file) != 1)
805 msg (ME, _("An error occurred while attempting to "
806 "write to a temporary file created as the "
807 "active file, while paging to disk: %s."),
815 vfm_source = &vfm_disk_stream;
822 /* Switch the memory stream from sink to source mode. */
824 memory_stream_mode (void)
826 /* Terminate the list. */
827 if (memory_sink_iter)
828 memory_sink_iter->next = NULL;
830 /* Sink --> source variables. */
831 memory_source_cases = memory_sink_cases;
832 memory_sink_cases = NULL;
835 /* Destroy all memory source data. */
837 memory_stream_destroy_source (void)
839 struct case_list *cur, *next;
841 for (cur = memory_source_cases; cur; cur = next)
846 memory_source_cases = NULL;
849 /* Destroy all memory sink data. */
851 memory_stream_destroy_sink (void)
853 struct case_list *cur, *next;
855 for (cur = memory_sink_cases; cur; cur = next)
860 memory_sink_cases = NULL;
864 struct case_stream vfm_memory_stream =
870 memory_stream_destroy_source,
871 memory_stream_destroy_sink,
875 #include "debug-print.h"
877 /* Add temp_case to the lag queue. */
881 if (lag_count < n_lag)
883 memcpy (lag_queue[lag_head], temp_case,
884 dict_get_case_size (temp_dict));
885 if (++lag_head >= n_lag)
889 /* Returns a pointer to the lagged case from N_BEFORE cases before the
890 current one, or NULL if there haven't been that many cases yet. */
892 lagged_case (int n_before)
894 assert (n_before <= n_lag);
895 if (n_before > lag_count)
899 int index = lag_head - n_before;
902 return lag_queue[index];
906 /* Transforms temp_case and writes it to the replacement active file
907 if advisable. Returns nonzero if more cases can be accepted, zero
908 otherwise. Do not call this function again after it has returned
911 procedure_write_case (write_case_data wc_data)
913 /* Index of current transformation. */
916 /* Return value: whether it's reasonable to write any more cases. */
919 debug_printf ((_("transform: ")));
924 /* Output the case if this is temp_trns. */
925 if (cur_trns == temp_trns)
927 debug_printf (("REC"));
932 vfm_sink_info.ncases++;
935 if (dict_get_case_limit (default_dict))
936 more_cases = (vfm_sink_info.ncases
937 < dict_get_case_limit (default_dict));
941 if (cur_trns >= n_trns)
944 debug_printf (("$%d", cur_trns));
946 /* Decide which transformation should come next. */
950 code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case);
954 /* Next transformation. */
958 /* Delete this case. */
961 /* Go to that transformation. */
968 /* Call the beginning of group function. */
969 if (!case_count && wc_data->beginfunc != NULL)
970 wc_data->beginfunc (wc_data->aux);
972 /* Call the procedure if there is one and FILTER and PROCESS IF
973 don't prohibit it. */
974 if (wc_data->procfunc != NULL && !exclude_this_case ())
975 wc_data->procfunc (temp_case, wc_data->aux);
980 debug_putc ('\n', stdout);
984 /* Return previously determined value. */
988 /* Clears the variables in the temporary case that need to be
989 cleared between processing cases. */
991 clear_temp_case (void)
993 /* FIXME? This is linear in the number of variables, but
994 doesn't need to be, so it's an easy optimization target. */
995 size_t var_cnt = dict_get_var_cnt (default_dict);
998 for (i = 0; i < var_cnt; i++)
1000 struct variable *v = dict_get_var (default_dict, i);
1001 if (v->init && v->reinit)
1003 if (v->type == NUMERIC)
1004 temp_case->data[v->fv].f = SYSMIS;
1006 memset (temp_case->data[v->fv].s, ' ', v->width);
1011 /* Returns nonzero if this case should be exclude as specified on
1012 FILTER or PROCESS IF, otherwise zero. */
1014 exclude_this_case (void)
1017 struct variable *filter_var = dict_get_filter (default_dict);
1018 if (filter_var != NULL)
1020 double f = temp_case->data[filter_var->fv].f;
1021 if (f == 0.0 || f == SYSMIS || is_num_user_missing (f, filter_var))
1026 if (process_if_expr != NULL
1027 && expr_evaluate (process_if_expr, temp_case, NULL) != 1.0)
1033 /* Appends TRNS to t_trns[], the list of all transformations to be
1034 performed on data as it is read from the active file. */
1036 add_transformation (struct trns_header * trns)
1038 if (n_trns >= m_trns)
1041 t_trns = xrealloc (t_trns, sizeof *t_trns * m_trns);
1043 t_trns[n_trns] = trns;
1044 trns->index = n_trns++;
1047 /* Cancels all active transformations, including any transformations
1048 created by the input program. */
1050 cancel_transformations (void)
1053 for (i = 0; i < n_trns; i++)
1055 if (t_trns[i]->free)
1056 t_trns[i]->free (t_trns[i]);
1059 n_trns = f_trns = 0;
1067 /* Dumps out the values of all the split variables for the case C. */
1069 dump_splits (struct ccase *c)
1071 struct variable *const *split;
1072 struct tab_table *t;
1076 split_cnt = dict_get_split_cnt (default_dict);
1077 t = tab_create (3, split_cnt + 1, 0);
1078 tab_dim (t, tab_natural_dimensions);
1079 tab_vline (t, TAL_1 | TAL_SPACING, 1, 0, split_cnt);
1080 tab_vline (t, TAL_1 | TAL_SPACING, 2, 0, split_cnt);
1081 tab_text (t, 0, 0, TAB_NONE, _("Variable"));
1082 tab_text (t, 1, 0, TAB_LEFT, _("Value"));
1083 tab_text (t, 2, 0, TAB_LEFT, _("Label"));
1084 split = dict_get_split_vars (default_dict);
1085 for (i = 0; i < split_cnt; i++)
1087 struct variable *v = split[i];
1089 const char *val_lab;
1091 assert (v->type == NUMERIC || v->type == ALPHA);
1092 tab_text (t, 0, i + 1, TAB_LEFT | TAT_PRINTF, "%s", v->name);
1094 data_out (temp_buf, &v->print, &c->data[v->fv]);
1096 temp_buf[v->print.w] = 0;
1097 tab_text (t, 1, i + 1, TAT_PRINTF, "%.*s", v->print.w, temp_buf);
1099 val_lab = val_labs_find (v->val_labs, c->data[v->fv]);
1101 tab_text (t, 2, i + 1, TAB_LEFT, val_lab);
1103 tab_flags (t, SOMF_NO_TITLE);
1107 /* This procfunc is substituted for the user-supplied procfunc when
1108 SPLIT FILE is active. This function forms a wrapper around that
1109 procfunc by dividing the input into series. */
1111 SPLIT_FILE_procfunc (struct ccase *c, void *data_)
1113 struct write_case_data *data = data_;
1114 static struct ccase *prev_case;
1115 struct variable *const *split;
1119 /* The first case always begins a new series. We also need to
1120 preserve the values of the case for later comparison. */
1121 if (case_count == 0)
1125 prev_case = xmalloc (vfm_sink_info.case_size);
1126 memcpy (prev_case, c, vfm_sink_info.case_size);
1129 if (data->beginfunc != NULL)
1130 data->beginfunc (data->aux);
1132 return data->procfunc (c, data->aux);
1135 /* Compare the value of each SPLIT FILE variable to the values on
1136 the previous case. */
1137 split = dict_get_split_vars (default_dict);
1138 split_cnt = dict_get_split_cnt (default_dict);
1139 for (i = 0; i < split_cnt; i++)
1141 struct variable *v = split[i];
1146 if (approx_ne (c->data[v->fv].f, prev_case->data[v->fv].f))
1150 if (memcmp (c->data[v->fv].s, prev_case->data[v->fv].s, v->width))
1157 return data->procfunc (c, data->aux);
1160 /* The values of the SPLIT FILE variable are different from the
1161 values on the previous case. That means that it's time to begin
1163 if (data->endfunc != NULL)
1164 data->endfunc (data->aux);
1166 if (data->beginfunc != NULL)
1167 data->beginfunc (data->aux);
1168 memcpy (prev_case, c, vfm_sink_info.case_size);
1169 return data->procfunc (c, data->aux);
1172 /* Case compaction. */
1174 /* Copies case SRC to case DEST, compacting it in the process. */
1176 compact_case (struct ccase *dest, const struct ccase *src)
1182 assert (compaction_necessary);
1186 if (dest != compaction_case)
1187 memcpy (dest, compaction_case, sizeof (union value) * compaction_nval);
1191 /* Copy all the variables except the scratch variables from SRC to
1193 var_cnt = dict_get_var_cnt (default_dict);
1194 for (i = 0; i < var_cnt; i++)
1196 struct variable *v = dict_get_var (default_dict, i);
1198 if (v->name[0] == '#')
1201 if (v->type == NUMERIC)
1202 dest->data[nval++] = src->data[v->fv];
1205 int w = DIV_RND_UP (v->width, sizeof (union value));
1207 memcpy (&dest->data[nval], &src->data[v->fv], w * sizeof (union value));
1213 /* Reassigns `fv' for each variable. Deletes scratch variables. */
1215 finish_compaction (void)
1219 for (i = 0; i < dict_get_var_cnt (default_dict); )
1221 struct variable *v = dict_get_var (default_dict, i);
1223 if (v->name[0] == '#')
1224 dict_delete_var (default_dict, v);
1228 dict_compact_values (default_dict);