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. */
40 #include "value-labels.h"
43 Virtual File Manager (vfm):
45 vfm is used to process data files. It uses the model that data is
46 read from one stream (the data source), then written to another
47 (the data sink). The data source is then deleted and the data sink
48 becomes the data source for the next procedure. */
50 #include "debug-print.h"
52 /* Procedure execution data. */
53 struct write_case_data
55 void (*beginfunc) (void *);
56 int (*procfunc) (struct ccase *, void *);
57 void (*endfunc) (void *);
61 /* This is used to read from the active file. */
62 struct case_stream *vfm_source;
64 /* This is used to write to the replacement active file. */
65 struct case_stream *vfm_sink;
67 /* Information about the data source. */
68 struct stream_info vfm_source_info;
70 /* Information about the data sink. */
71 struct stream_info vfm_sink_info;
73 /* Nonzero if the case needs to have values deleted before being
74 stored, zero otherwise. */
75 int compaction_necessary;
77 /* Number of values after compaction, or the same as
78 vfm_sink_info.nval, if compaction is not necessary. */
81 /* Temporary case buffer with enough room for `compaction_nval'
83 struct ccase *compaction_case;
85 /* Within a session, when paging is turned on, it is never turned back
86 off. This policy might be too aggressive. */
87 static int paging = 0;
89 /* Time at which vfm was last invoked. */
90 time_t last_vfm_invocation;
92 /* Number of cases passed to proc_func(). */
93 static int case_count;
96 int n_lag; /* Number of cases to lag. */
97 static int lag_count; /* Number of cases in lag_queue so far. */
98 static int lag_head; /* Index where next case will be added. */
99 static struct ccase **lag_queue; /* Array of n_lag ccase * elements. */
101 static void open_active_file (void);
102 static void close_active_file (struct write_case_data *);
103 static int SPLIT_FILE_procfunc (struct ccase *, void *);
104 static void finish_compaction (void);
105 static void lag_case (void);
106 static int procedure_write_case (struct write_case_data *);
107 static void clear_temp_case (void);
108 static int exclude_this_case (void);
110 /* Public functions. */
112 /* Reads all the cases from the active file, transforms them by
113 the active set of transformations, calls PROCFUNC with CURCASE
114 set to the case, and writes them to a new active file.
116 Divides the active file into zero or more series of one or more
117 cases each. BEGINFUNC is called before each series. ENDFUNC is
118 called after each series.
120 Arbitrary user-specified data AUX is passed to BEGINFUNC,
121 PROCFUNC, and ENDFUNC as auxiliary data. */
123 procedure (void (*beginfunc) (void *),
124 int (*procfunc) (struct ccase *curcase, void *),
125 void (*endfunc) (void *),
128 static int recursive_call;
130 struct write_case_data procedure_write_data;
131 struct write_case_data split_file_data;
133 assert (++recursive_call == 1);
135 if (dict_get_split_cnt (default_dict) == 0)
137 /* Normally we just use the data passed by the user. */
138 procedure_write_data.beginfunc = beginfunc;
139 procedure_write_data.procfunc = procfunc;
140 procedure_write_data.endfunc = endfunc;
141 procedure_write_data.aux = aux;
145 /* Under SPLIT FILE, we add a layer of indirection. */
146 procedure_write_data.beginfunc = NULL;
147 procedure_write_data.procfunc = SPLIT_FILE_procfunc;
148 procedure_write_data.endfunc = endfunc;
149 procedure_write_data.aux = &split_file_data;
151 split_file_data.beginfunc = beginfunc;
152 split_file_data.procfunc = procfunc;
153 split_file_data.endfunc = endfunc;
154 split_file_data.aux = aux;
157 last_vfm_invocation = time (NULL);
160 vfm_source->read (procedure_write_case, &procedure_write_data);
161 close_active_file (&procedure_write_data);
163 assert (--recursive_call == 0);
166 /* Active file processing support. Subtly different semantics from
169 static int process_active_file_write_case (struct write_case_data *data);
171 /* The casefunc might want us to stop calling it. */
172 static int not_canceled;
174 /* Reads all the cases from the active file and passes them one-by-one
175 to CASEFUNC in temp_case. Before any cases are passed, calls
176 BEGINFUNC. After all the cases have been passed, calls ENDFUNC.
177 BEGINFUNC, CASEFUNC, and ENDFUNC can write temp_case to the output
178 file by calling process_active_file_output_case().
180 process_active_file() ignores TEMPORARY, SPLIT FILE, and N. */
182 process_active_file (void (*beginfunc) (void *),
183 int (*casefunc) (struct ccase *curcase, void *),
184 void (*endfunc) (void *),
187 struct write_case_data process_active_write_data;
189 process_active_write_data.beginfunc = beginfunc;
190 process_active_write_data.procfunc = casefunc;
191 process_active_write_data.endfunc = endfunc;
192 process_active_write_data.aux = aux;
199 /* There doesn't necessarily need to be an active file. */
201 vfm_source->read (process_active_file_write_case,
202 &process_active_write_data);
205 close_active_file (&process_active_write_data);
208 /* Pass the current case to casefunc. */
210 process_active_file_write_case (struct write_case_data *data)
212 /* Index of current transformation. */
215 for (cur_trns = f_trns ; cur_trns != temp_trns; )
219 code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case);
223 /* Next transformation. */
227 /* Delete this case. */
230 /* Go to that transformation. */
239 /* Call the procedure if FILTER and PROCESS IF don't prohibit it. */
240 if (not_canceled && !exclude_this_case ())
241 not_canceled = data->procfunc (temp_case, data->aux);
251 /* Write temp_case to the active file. */
253 process_active_file_output_case (void)
255 vfm_sink_info.ncases++;
259 /* Opening the active file. */
261 /* It might be usefully noted that the following several functions are
262 given in the order that they are called by open_active_file(). */
264 /* Prepare to write to the replacement active file. */
266 prepare_for_writing (void)
268 /* FIXME: If ALL the conditions listed below hold true, then the
269 replacement active file is guaranteed to be identical to the
270 original active file:
272 1. TEMPORARY was the first transformation, OR, there were no
273 transformations at all.
275 2. Input is not coming from an input program.
277 3. Compaction is not necessary.
279 So, in this case, we shouldn't have to replace the active
280 file--it's just a waste of time and space. */
282 vfm_sink_info.ncases = 0;
283 vfm_sink_info.nval = dict_get_next_value_idx (default_dict);
284 vfm_sink_info.case_size = dict_get_case_size (default_dict);
286 if (vfm_sink == NULL)
288 if (vfm_sink_info.case_size * vfm_source_info.ncases > MAX_WORKSPACE
291 msg (MW, _("Workspace overflow predicted. Max workspace is "
292 "currently set to %d KB (%d cases at %d bytes each). "
293 "Paging active file to disk."),
294 MAX_WORKSPACE / 1024, MAX_WORKSPACE / vfm_sink_info.case_size,
295 vfm_sink_info.case_size);
300 vfm_sink = paging ? &vfm_disk_stream : &vfm_memory_stream;
304 /* Arrange for compacting the output cases for storage. */
306 arrange_compaction (void)
308 int count_values = 0;
313 /* Count up the number of `value's that will be output. */
314 for (i = 0; i < dict_get_var_cnt (temp_dict); i++)
316 struct variable *v = dict_get_var (temp_dict, i);
318 if (v->name[0] != '#')
321 count_values += v->nv;
324 assert (temporary == 2
325 || count_values <= dict_get_next_value_idx (temp_dict));
328 /* Compaction is only necessary if the number of `value's to output
329 differs from the number already present. */
330 compaction_nval = count_values;
331 if (temporary == 2 || count_values != dict_get_next_value_idx (temp_dict))
332 compaction_necessary = 1;
334 compaction_necessary = 0;
340 /* Prepares the temporary case and compaction case. */
342 make_temp_case (void)
344 temp_case = xmalloc (vfm_sink_info.case_size);
346 if (compaction_necessary)
347 compaction_case = xmalloc (sizeof (struct ccase)
348 + sizeof (union value) * (compaction_nval - 1));
352 /* Returns the name of the variable that owns the index CCASE_INDEX
355 index_to_varname (int ccase_index)
359 for (i = 0; i < default_dict.nvar; i++)
361 struct variable *v = default_dict.var[i];
363 if (ccase_index >= v->fv && ccase_index < v->fv + v->nv)
364 return default_dict.var[i]->name;
370 /* Initializes temp_case from the vectors that say which `value's
371 need to be initialized just once, and which ones need to be
372 re-initialized before every case. */
374 vector_initialization (void)
376 size_t var_cnt = dict_get_var_cnt (default_dict);
379 for (i = 0; i < var_cnt; i++)
381 struct variable *v = dict_get_var (default_dict, i);
383 if (v->type == NUMERIC)
386 temp_case->data[v->fv].f = 0.0;
388 temp_case->data[v->fv].f = SYSMIS;
391 memset (temp_case->data[v->fv].s, ' ', v->width);
395 /* Sets all the lag-related variables based on value of n_lag. */
406 lag_queue = xmalloc (n_lag * sizeof *lag_queue);
407 for (i = 0; i < n_lag; i++)
408 lag_queue[i] = xmalloc (dict_get_case_size (temp_dict));
411 /* There is a lot of potential confusion in the vfm and related
412 routines over the number of `value's at each stage of the process.
413 Here is each nval count, with explanation, as set up by
416 vfm_source_info.nval: Number of `value's in the cases returned by
417 the source stream. This value turns out not to be very useful, but
418 we maintain it anyway.
420 vfm_sink_info.nval: Number of `value's in the cases after all
421 transformations have been performed. Never less than
422 vfm_source_info.nval.
424 temp_dict->nval: Number of `value's in the cases after the
425 transformations leading up to TEMPORARY have been performed. If
426 TEMPORARY was not specified, this is equal to vfm_sink_info.nval.
427 Never less than vfm_sink_info.nval.
429 compaction_nval: Number of `value's in the cases after the
430 transformations leading up to TEMPORARY have been performed and the
431 case has been compacted by compact_case(), if compaction is
432 necessary. This the number of `value's in the cases saved by the
433 sink stream. (However, note that the cases passed to the sink
434 stream have not yet been compacted. It is the responsibility of
435 the data sink to call compact_case().) This may be less than,
436 greater than, or equal to vfm_source_info.nval. `compaction'
437 becomes the new value of default_dict.nval after the procedure is
440 default_dict.nval: This is often an alias for temp_dict->nval. As
441 such it can really have no separate existence until the procedure
442 is complete. For this reason it should *not* be referenced inside
443 the execution of a procedure. */
444 /* Makes all preparations for reading from the data source and writing
447 open_active_file (void)
449 /* Sometimes we want to refer to the dictionary that applies to the
450 data actually written to the sink. This is either temp_dict or
451 default_dict. However, if TEMPORARY is not on, then temp_dict
452 does not apply. So, we can set temp_dict to default_dict in this
457 temp_dict = default_dict;
460 /* No cases passed to the procedure yet. */
464 prepare_for_writing ();
465 arrange_compaction ();
467 vector_initialization ();
468 discard_ctl_stack ();
472 debug_printf (("vfm: reading from %s source, writing to %s sink.\n",
473 vfm_source->name, vfm_sink->name));
474 debug_printf (("vfm: vfm_source_info.nval=%d, vfm_sink_info.nval=%d, "
475 "temp_dict->nval=%d, compaction_nval=%d, "
476 "default_dict.nval=%d\n",
477 vfm_source_info.nval, vfm_sink_info.nval, temp_dict->nval,
478 compaction_nval, default_dict.nval));
481 /* Closes the active file. */
483 close_active_file (struct write_case_data *data)
485 /* Close the current case group. */
486 if (case_count && data->endfunc != NULL)
487 data->endfunc (data->aux);
489 /* Stop lagging (catch up?). */
494 for (i = 0; i < n_lag; i++)
500 /* Assume the dictionary from right before TEMPORARY, if any. Turn
504 dict_destroy (default_dict);
505 default_dict = temp_dict;
509 /* Finish compaction. */
510 if (compaction_necessary)
511 finish_compaction ();
513 /* Old data sink --> New data source. */
514 if (vfm_source && vfm_source->destroy_source)
515 vfm_source->destroy_source ();
517 vfm_source = vfm_sink;
518 vfm_source_info.ncases = vfm_sink_info.ncases;
519 vfm_source_info.nval = compaction_nval;
520 vfm_source_info.case_size = (sizeof (struct ccase)
521 + (compaction_nval - 1) * sizeof (union value));
522 if (vfm_source->mode)
525 /* Old data sink is gone now. */
528 /* Cancel TEMPORARY. */
531 /* Free temporary cases. */
535 free (compaction_case);
536 compaction_case = NULL;
538 /* Cancel PROCESS IF. */
539 expr_free (process_if_expr);
540 process_if_expr = NULL;
542 /* Cancel FILTER if temporary. */
543 if (dict_get_filter (default_dict) != NULL && !FILTER_before_TEMPORARY)
544 dict_set_filter (default_dict, NULL);
546 /* Cancel transformations. */
547 cancel_transformations ();
549 /* Turn off case limiter. */
550 dict_set_case_limit (default_dict, 0);
552 /* Clear VECTOR vectors. */
553 dict_clear_vectors (default_dict);
555 debug_printf (("vfm: procedure complete\n\n"));
558 /* Disk case stream. */
560 /* Associated files. */
561 FILE *disk_source_file;
562 FILE *disk_sink_file;
564 /* Initializes the disk sink. */
566 disk_stream_init (void)
568 disk_sink_file = tmpfile ();
571 msg (ME, _("An error occurred attempting to create a temporary "
572 "file for use as the active file: %s."),
578 /* Reads all cases from the disk source and passes them one by one to
581 disk_stream_read (write_case_func *write_case, write_case_data wc_data)
585 for (i = 0; i < vfm_source_info.ncases; i++)
587 if (!fread (temp_case, vfm_source_info.case_size, 1, disk_source_file))
589 msg (ME, _("An error occurred while attempting to read from "
590 "a temporary file created for the active file: %s."),
596 if (!write_case (wc_data))
601 /* Writes temp_case to the disk sink. */
603 disk_stream_write (void)
605 union value *src_case;
607 if (compaction_necessary)
609 compact_case (compaction_case, temp_case);
610 src_case = (union value *) compaction_case;
612 else src_case = (union value *) temp_case;
614 if (fwrite (src_case, sizeof *src_case * compaction_nval, 1,
615 disk_sink_file) != 1)
617 msg (ME, _("An error occurred while attempting to write to a "
618 "temporary file used as the active file: %s."),
624 /* Switches the stream from a sink to a source. */
626 disk_stream_mode (void)
628 /* Rewind the sink. */
629 if (fseek (disk_sink_file, 0, SEEK_SET) != 0)
631 msg (ME, _("An error occurred while attempting to rewind a "
632 "temporary file used as the active file: %s."),
637 /* Sink --> source variables. */
638 disk_source_file = disk_sink_file;
641 /* Destroys the source's internal data. */
643 disk_stream_destroy_source (void)
645 if (disk_source_file)
647 fclose (disk_source_file);
648 disk_source_file = NULL;
652 /* Destroys the sink's internal data. */
654 disk_stream_destroy_sink (void)
658 fclose (disk_sink_file);
659 disk_sink_file = NULL;
664 struct case_stream vfm_disk_stream =
670 disk_stream_destroy_source,
671 disk_stream_destroy_sink,
675 /* Memory case stream. */
677 /* List of cases stored in the stream. */
678 struct case_list *memory_source_cases;
679 struct case_list *memory_sink_cases;
682 struct case_list *memory_sink_iter;
684 /* Maximum number of cases. */
685 int memory_sink_max_cases;
687 /* Initializes the memory stream variables for writing. */
689 memory_stream_init (void)
691 memory_sink_cases = NULL;
692 memory_sink_iter = NULL;
694 assert (compaction_nval);
695 memory_sink_max_cases = MAX_WORKSPACE / (sizeof (union value) * compaction_nval);
698 /* Reads the case stream from memory and passes it to write_case(). */
700 memory_stream_read (write_case_func *write_case, write_case_data wc_data)
702 while (memory_source_cases != NULL)
704 memcpy (temp_case, &memory_source_cases->c, vfm_source_info.case_size);
707 struct case_list *current = memory_source_cases;
708 memory_source_cases = memory_source_cases->next;
712 if (!write_case (wc_data))
717 /* Writes temp_case to the memory stream. */
719 memory_stream_write (void)
721 struct case_list *new_case = malloc (sizeof (struct case_list)
722 + ((compaction_nval - 1)
723 * sizeof (union value)));
725 /* If we've got memory to spare then add it to the linked list. */
726 if (vfm_sink_info.ncases <= memory_sink_max_cases && new_case != NULL)
728 if (compaction_necessary)
729 compact_case (&new_case->c, temp_case);
731 memcpy (&new_case->c, temp_case, sizeof (union value) * compaction_nval);
733 /* Append case to linked list. */
734 if (memory_sink_cases)
735 memory_sink_iter = memory_sink_iter->next = new_case;
737 memory_sink_iter = memory_sink_cases = new_case;
741 /* Out of memory. Write the active file to disk. */
742 struct case_list *cur, *next;
744 /* Notify the user. */
746 msg (MW, _("Virtual memory exhausted. Paging active file "
749 msg (MW, _("Workspace limit of %d KB (%d cases at %d bytes each) "
750 "overflowed. Paging active file to disk."),
751 MAX_WORKSPACE / 1024, memory_sink_max_cases,
752 compaction_nval * sizeof (union value));
756 /* Switch to a disk sink. */
757 vfm_sink = &vfm_disk_stream;
761 /* Terminate the list. */
762 if (memory_sink_iter)
763 memory_sink_iter->next = NULL;
765 /* Write the cases to disk and destroy them. We can't call
766 vfm->sink->write() because of compaction. */
767 for (cur = memory_sink_cases; cur; cur = next)
770 if (fwrite (cur->c.data, sizeof (union value) * compaction_nval, 1,
771 disk_sink_file) != 1)
773 msg (ME, _("An error occurred while attempting to "
774 "write to a temporary file created as the "
775 "active file, while paging to disk: %s."),
782 /* Write the current case to disk. */
787 /* If the data is stored in memory, causes it to be written to disk.
788 To be called only *between* procedure()s, not within them. */
792 if (vfm_source == &vfm_memory_stream)
794 /* Switch to a disk sink. */
795 vfm_sink = &vfm_disk_stream;
799 /* Write the cases to disk and destroy them. We can't call
800 vfm->sink->write() because of compaction. */
802 struct case_list *cur, *next;
804 for (cur = memory_source_cases; cur; cur = next)
807 if (fwrite (cur->c.data, sizeof *cur->c.data * compaction_nval, 1,
808 disk_sink_file) != 1)
810 msg (ME, _("An error occurred while attempting to "
811 "write to a temporary file created as the "
812 "active file, while paging to disk: %s."),
820 vfm_source = &vfm_disk_stream;
827 /* Switch the memory stream from sink to source mode. */
829 memory_stream_mode (void)
831 /* Terminate the list. */
832 if (memory_sink_iter)
833 memory_sink_iter->next = NULL;
835 /* Sink --> source variables. */
836 memory_source_cases = memory_sink_cases;
837 memory_sink_cases = NULL;
840 /* Destroy all memory source data. */
842 memory_stream_destroy_source (void)
844 struct case_list *cur, *next;
846 for (cur = memory_source_cases; cur; cur = next)
851 memory_source_cases = NULL;
854 /* Destroy all memory sink data. */
856 memory_stream_destroy_sink (void)
858 struct case_list *cur, *next;
860 for (cur = memory_sink_cases; cur; cur = next)
865 memory_sink_cases = NULL;
869 struct case_stream vfm_memory_stream =
875 memory_stream_destroy_source,
876 memory_stream_destroy_sink,
880 #include "debug-print.h"
882 /* Add temp_case to the lag queue. */
886 if (lag_count < n_lag)
888 memcpy (lag_queue[lag_head], temp_case,
889 dict_get_case_size (temp_dict));
890 if (++lag_head >= n_lag)
894 /* Returns a pointer to the lagged case from N_BEFORE cases before the
895 current one, or NULL if there haven't been that many cases yet. */
897 lagged_case (int n_before)
899 assert (n_before <= n_lag);
900 if (n_before > lag_count)
904 int index = lag_head - n_before;
907 return lag_queue[index];
911 /* Transforms temp_case and writes it to the replacement active file
912 if advisable. Returns nonzero if more cases can be accepted, zero
913 otherwise. Do not call this function again after it has returned
916 procedure_write_case (write_case_data wc_data)
918 /* Index of current transformation. */
921 /* Return value: whether it's reasonable to write any more cases. */
924 debug_printf ((_("transform: ")));
929 /* Output the case if this is temp_trns. */
930 if (cur_trns == temp_trns)
932 debug_printf (("REC"));
937 vfm_sink_info.ncases++;
940 if (dict_get_case_limit (default_dict))
941 more_cases = (vfm_sink_info.ncases
942 < dict_get_case_limit (default_dict));
946 if (cur_trns >= n_trns)
949 debug_printf (("$%d", cur_trns));
951 /* Decide which transformation should come next. */
955 code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case);
959 /* Next transformation. */
963 /* Delete this case. */
966 /* Go to that transformation. */
973 /* Call the beginning of group function. */
974 if (!case_count && wc_data->beginfunc != NULL)
975 wc_data->beginfunc (wc_data->aux);
977 /* Call the procedure if there is one and FILTER and PROCESS IF
978 don't prohibit it. */
979 if (wc_data->procfunc != NULL && !exclude_this_case ())
980 wc_data->procfunc (temp_case, wc_data->aux);
985 debug_putc ('\n', stdout);
989 /* Return previously determined value. */
993 /* Clears the variables in the temporary case that need to be
994 cleared between processing cases. */
996 clear_temp_case (void)
998 /* FIXME? This is linear in the number of variables, but
999 doesn't need to be, so it's an easy optimization target. */
1000 size_t var_cnt = dict_get_var_cnt (default_dict);
1003 for (i = 0; i < var_cnt; i++)
1005 struct variable *v = dict_get_var (default_dict, i);
1006 if (v->init && v->reinit)
1008 if (v->type == NUMERIC)
1009 temp_case->data[v->fv].f = SYSMIS;
1011 memset (temp_case->data[v->fv].s, ' ', v->width);
1016 /* Returns nonzero if this case should be exclude as specified on
1017 FILTER or PROCESS IF, otherwise zero. */
1019 exclude_this_case (void)
1022 struct variable *filter_var = dict_get_filter (default_dict);
1023 if (filter_var != NULL)
1025 double f = temp_case->data[filter_var->fv].f;
1026 if (f == 0.0 || f == SYSMIS || is_num_user_missing (f, filter_var))
1031 if (process_if_expr != NULL
1032 && expr_evaluate (process_if_expr, temp_case, NULL) != 1.0)
1038 /* Appends TRNS to t_trns[], the list of all transformations to be
1039 performed on data as it is read from the active file. */
1041 add_transformation (struct trns_header * trns)
1043 if (n_trns >= m_trns)
1046 t_trns = xrealloc (t_trns, sizeof *t_trns * m_trns);
1048 t_trns[n_trns] = trns;
1049 trns->index = n_trns++;
1052 /* Cancels all active transformations, including any transformations
1053 created by the input program. */
1055 cancel_transformations (void)
1058 for (i = 0; i < n_trns; i++)
1060 if (t_trns[i]->free)
1061 t_trns[i]->free (t_trns[i]);
1064 n_trns = f_trns = 0;
1072 /* Dumps out the values of all the split variables for the case C. */
1074 dump_splits (struct ccase *c)
1076 struct variable *const *split;
1077 struct tab_table *t;
1081 split_cnt = dict_get_split_cnt (default_dict);
1082 t = tab_create (3, split_cnt + 1, 0);
1083 tab_dim (t, tab_natural_dimensions);
1084 tab_vline (t, TAL_1 | TAL_SPACING, 1, 0, split_cnt);
1085 tab_vline (t, TAL_1 | TAL_SPACING, 2, 0, split_cnt);
1086 tab_text (t, 0, 0, TAB_NONE, _("Variable"));
1087 tab_text (t, 1, 0, TAB_LEFT, _("Value"));
1088 tab_text (t, 2, 0, TAB_LEFT, _("Label"));
1089 split = dict_get_split_vars (default_dict);
1090 for (i = 0; i < split_cnt; i++)
1092 struct variable *v = split[i];
1094 const char *val_lab;
1096 assert (v->type == NUMERIC || v->type == ALPHA);
1097 tab_text (t, 0, i + 1, TAB_LEFT | TAT_PRINTF, "%s", v->name);
1099 data_out (temp_buf, &v->print, &c->data[v->fv]);
1101 temp_buf[v->print.w] = 0;
1102 tab_text (t, 1, i + 1, TAT_PRINTF, "%.*s", v->print.w, temp_buf);
1104 val_lab = val_labs_find (v->val_labs, c->data[v->fv]);
1106 tab_text (t, 2, i + 1, TAB_LEFT, val_lab);
1108 tab_flags (t, SOMF_NO_TITLE);
1112 /* This procfunc is substituted for the user-supplied procfunc when
1113 SPLIT FILE is active. This function forms a wrapper around that
1114 procfunc by dividing the input into series. */
1116 SPLIT_FILE_procfunc (struct ccase *c, void *data_)
1118 struct write_case_data *data = data_;
1119 static struct ccase *prev_case;
1120 struct variable *const *split;
1124 /* The first case always begins a new series. We also need to
1125 preserve the values of the case for later comparison. */
1126 if (case_count == 0)
1130 prev_case = xmalloc (vfm_sink_info.case_size);
1131 memcpy (prev_case, c, vfm_sink_info.case_size);
1134 if (data->beginfunc != NULL)
1135 data->beginfunc (data->aux);
1137 return data->procfunc (c, data->aux);
1140 /* Compare the value of each SPLIT FILE variable to the values on
1141 the previous case. */
1142 split = dict_get_split_vars (default_dict);
1143 split_cnt = dict_get_split_cnt (default_dict);
1144 for (i = 0; i < split_cnt; i++)
1146 struct variable *v = split[i];
1151 if (c->data[v->fv].f != prev_case->data[v->fv].f)
1155 if (memcmp (c->data[v->fv].s, prev_case->data[v->fv].s, v->width))
1162 return data->procfunc (c, data->aux);
1165 /* The values of the SPLIT FILE variable are different from the
1166 values on the previous case. That means that it's time to begin
1168 if (data->endfunc != NULL)
1169 data->endfunc (data->aux);
1171 if (data->beginfunc != NULL)
1172 data->beginfunc (data->aux);
1173 memcpy (prev_case, c, vfm_sink_info.case_size);
1174 return data->procfunc (c, data->aux);
1177 /* Case compaction. */
1179 /* Copies case SRC to case DEST, compacting it in the process. */
1181 compact_case (struct ccase *dest, const struct ccase *src)
1187 assert (compaction_necessary);
1191 if (dest != compaction_case)
1192 memcpy (dest, compaction_case, sizeof (union value) * compaction_nval);
1196 /* Copy all the variables except the scratch variables from SRC to
1198 var_cnt = dict_get_var_cnt (default_dict);
1199 for (i = 0; i < var_cnt; i++)
1201 struct variable *v = dict_get_var (default_dict, i);
1203 if (v->name[0] == '#')
1206 if (v->type == NUMERIC)
1207 dest->data[nval++] = src->data[v->fv];
1210 int w = DIV_RND_UP (v->width, sizeof (union value));
1212 memcpy (&dest->data[nval], &src->data[v->fv], w * sizeof (union value));
1218 /* Reassigns `fv' for each variable. Deletes scratch variables. */
1220 finish_compaction (void)
1224 for (i = 0; i < dict_get_var_cnt (default_dict); )
1226 struct variable *v = dict_get_var (default_dict, i);
1228 if (v->name[0] == '#')
1229 dict_delete_var (default_dict, v);
1233 dict_compact_values (default_dict);