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 /* Filter variable and `value' index. */
75 static struct variable *filter_var;
76 static int filter_index;
80 && (temp_case->data[filter_index].f == 0.0 \
81 || temp_case->data[filter_index].f == SYSMIS \
82 || is_num_user_missing (temp_case->data[filter_index].f, \
85 /* Nonzero if the case needs to have values deleted before being
86 stored, zero otherwise. */
87 int compaction_necessary;
89 /* Number of values after compaction, or the same as
90 vfm_sink_info.nval, if compaction is not necessary. */
93 /* Temporary case buffer with enough room for `compaction_nval'
95 struct ccase *compaction_case;
97 /* Within a session, when paging is turned on, it is never turned back
98 off. This policy might be too aggressive. */
99 static int paging = 0;
101 /* Time at which vfm was last invoked. */
102 time_t last_vfm_invocation;
104 /* Number of cases passed to proc_func(). */
105 static int case_count;
108 int n_lag; /* Number of cases to lag. */
109 static int lag_count; /* Number of cases in lag_queue so far. */
110 static int lag_head; /* Index where next case will be added. */
111 static struct ccase **lag_queue; /* Array of n_lag ccase * elements. */
113 static void open_active_file (void);
114 static void close_active_file (struct write_case_data *);
115 static int SPLIT_FILE_procfunc (struct ccase *, void *);
116 static void finish_compaction (void);
117 static void lag_case (void);
118 static int procedure_write_case (struct write_case_data *);
119 static void clear_temp_case (void);
121 /* Public functions. */
123 /* Reads all the cases from the active file, transforms them by
124 the active set of transformations, calls PROCFUNC with CURCASE
125 set to the case , and writes them to a new active file.
127 Divides the active file into zero or more series of one or more
128 cases each. BEGINFUNC is called before each series. ENDFUNC is
129 called after each series.
131 Arbitrary user-specified data AUX is passed to BEGINFUNC,
132 PROCFUNC, and ENDFUNC as auxiliary data. */
134 procedure (void (*beginfunc) (void *),
135 int (*procfunc) (struct ccase *curcase, void *),
136 void (*endfunc) (void *),
139 struct write_case_data procedure_write_data;
140 struct write_case_data split_file_data;
142 if (dict_get_split_cnt (default_dict) == 0)
144 /* Normally we just use the data passed by the user. */
145 procedure_write_data.beginfunc = beginfunc;
146 procedure_write_data.procfunc = procfunc;
147 procedure_write_data.endfunc = endfunc;
148 procedure_write_data.aux = aux;
152 /* Under SPLIT FILE, we add a layer of indirection. */
153 procedure_write_data.beginfunc = NULL;
154 procedure_write_data.procfunc = SPLIT_FILE_procfunc;
155 procedure_write_data.endfunc = endfunc;
156 procedure_write_data.aux = &split_file_data;
158 split_file_data.beginfunc = beginfunc;
159 split_file_data.procfunc = procfunc;
160 split_file_data.endfunc = endfunc;
161 split_file_data.aux = aux;
164 last_vfm_invocation = time (NULL);
167 vfm_source->read (procedure_write_case, &procedure_write_data);
168 close_active_file (&procedure_write_data);
171 /* Active file processing support. Subtly different semantics from
174 static int process_active_file_write_case (struct write_case_data *data);
176 /* The casefunc might want us to stop calling it. */
177 static int not_canceled;
179 /* Reads all the cases from the active file and passes them one-by-one
180 to CASEFUNC in temp_case. Before any cases are passed, calls
181 BEGINFUNC. After all the cases have been passed, calls ENDFUNC.
182 BEGINFUNC, CASEFUNC, and ENDFUNC can write temp_case to the output
183 file by calling process_active_file_output_case().
185 process_active_file() ignores TEMPORARY, SPLIT FILE, and N. */
187 process_active_file (void (*beginfunc) (void *),
188 int (*casefunc) (struct ccase *curcase, void *),
189 void (*endfunc) (void *),
192 struct write_case_data process_active_write_data;
194 process_active_write_data.beginfunc = beginfunc;
195 process_active_write_data.procfunc = casefunc;
196 process_active_write_data.endfunc = endfunc;
197 process_active_write_data.aux = aux;
204 /* There doesn't necessarily need to be an active file. */
206 vfm_source->read (process_active_file_write_case,
207 &process_active_write_data);
210 close_active_file (&process_active_write_data);
213 /* Pass the current case to casefunc. */
215 process_active_file_write_case (struct write_case_data *data)
217 /* Index of current transformation. */
220 for (cur_trns = f_trns ; cur_trns != temp_trns; )
224 code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case);
228 /* Next transformation. */
232 /* Delete this case. */
235 /* Go to that transformation. */
244 /* Call the procedure if FILTER and PROCESS IF don't prohibit it. */
247 && (process_if_expr == NULL ||
248 expr_evaluate (process_if_expr, temp_case, NULL) == 1.0))
249 not_canceled = data->procfunc (temp_case, data->aux);
259 /* Write temp_case to the active file. */
261 process_active_file_output_case (void)
263 vfm_sink_info.ncases++;
267 /* Opening the active file. */
269 /* It might be usefully noted that the following several functions are
270 given in the order that they are called by open_active_file(). */
272 /* Prepare to write to the replacement active file. */
274 prepare_for_writing (void)
276 /* FIXME: If ALL the conditions listed below hold true, then the
277 replacement active file is guaranteed to be identical to the
278 original active file:
280 1. TEMPORARY was the first transformation, OR, there were no
281 transformations at all.
283 2. Input is not coming from an input program.
285 3. Compaction is not necessary.
287 So, in this case, we shouldn't have to replace the active
288 file--it's just a waste of time and space. */
290 vfm_sink_info.ncases = 0;
291 vfm_sink_info.nval = dict_get_value_cnt (default_dict);
292 vfm_sink_info.case_size = (sizeof (struct ccase)
293 + ((dict_get_value_cnt (default_dict) - 1)
294 * sizeof (union value)));
296 if (vfm_sink == NULL)
298 if (vfm_sink_info.case_size * vfm_source_info.ncases > MAX_WORKSPACE
301 msg (MW, _("Workspace overflow predicted. Max workspace is "
302 "currently set to %d KB (%d cases at %d bytes each). "
303 "Paging active file to disk."),
304 MAX_WORKSPACE / 1024, MAX_WORKSPACE / vfm_sink_info.case_size,
305 vfm_sink_info.case_size);
310 vfm_sink = paging ? &vfm_disk_stream : &vfm_memory_stream;
314 /* Arrange for compacting the output cases for storage. */
316 arrange_compaction (void)
318 int count_values = 0;
323 /* Count up the number of `value's that will be output. */
324 for (i = 0; i < dict_get_var_cnt (temp_dict); i++)
326 struct variable *v = dict_get_var (temp_dict, i);
328 if (v->name[0] != '#')
331 count_values += v->nv;
334 assert (temporary == 2 || count_values <= dict_get_value_cnt (temp_dict));
337 /* Compaction is only necessary if the number of `value's to output
338 differs from the number already present. */
339 compaction_nval = count_values;
340 compaction_necessary = (temporary == 2
341 || count_values != dict_get_value_cnt (temp_dict));
347 /* Prepares the temporary case and compaction case. */
349 make_temp_case (void)
351 temp_case = xmalloc (vfm_sink_info.case_size);
353 if (compaction_necessary)
354 compaction_case = xmalloc (sizeof (struct ccase)
355 + sizeof (union value) * (compaction_nval - 1));
359 /* Returns the name of the variable that owns the index CCASE_INDEX
362 index_to_varname (int ccase_index)
366 for (i = 0; i < default_dict.nvar; i++)
368 struct variable *v = default_dict.var[i];
370 if (ccase_index >= v->fv && ccase_index < v->fv + v->nv)
371 return default_dict.var[i]->name;
377 /* Initializes temp_case from the vectors that say which `value's
378 need to be initialized just once, and which ones need to be
379 re-initialized before every case. */
381 vector_initialization (void)
383 size_t var_cnt = dict_get_var_cnt (default_dict);
386 for (i = 0; i < var_cnt; i++)
388 struct variable *v = dict_get_var (default_dict, i);
390 if (v->type == NUMERIC)
393 temp_case->data[v->fv].f = 0.0;
395 temp_case->data[v->fv].f = SYSMIS;
398 memset (temp_case->data[v->fv].s, ' ', v->width);
402 /* Sets filter_index to an appropriate value. */
406 filter_var = dict_get_filter (default_dict);
408 if (filter_var != NULL)
410 assert (filter_var->type == NUMERIC);
411 filter_index = filter_var->index;
417 /* Sets all the lag-related variables based on value of n_lag. */
428 lag_queue = xmalloc (n_lag * sizeof *lag_queue);
429 for (i = 0; i < n_lag; i++)
430 lag_queue[i] = xmalloc (dict_get_value_cnt (temp_dict)
431 * sizeof **lag_queue);
434 /* There is a lot of potential confusion in the vfm and related
435 routines over the number of `value's at each stage of the process.
436 Here is each nval count, with explanation, as set up by
439 vfm_source_info.nval: Number of `value's in the cases returned by
440 the source stream. This value turns out not to be very useful, but
441 we maintain it anyway.
443 vfm_sink_info.nval: Number of `value's in the cases after all
444 transformations have been performed. Never less than
445 vfm_source_info.nval.
447 temp_dict->nval: Number of `value's in the cases after the
448 transformations leading up to TEMPORARY have been performed. If
449 TEMPORARY was not specified, this is equal to vfm_sink_info.nval.
450 Never less than vfm_sink_info.nval.
452 compaction_nval: Number of `value's in the cases after the
453 transformations leading up to TEMPORARY have been performed and the
454 case has been compacted by compact_case(), if compaction is
455 necessary. This the number of `value's in the cases saved by the
456 sink stream. (However, note that the cases passed to the sink
457 stream have not yet been compacted. It is the responsibility of
458 the data sink to call compact_case().) This may be less than,
459 greater than, or equal to vfm_source_info.nval. `compaction'
460 becomes the new value of default_dict.nval after the procedure is
463 default_dict.nval: This is often an alias for temp_dict->nval. As
464 such it can really have no separate existence until the procedure
465 is complete. For this reason it should *not* be referenced inside
466 the execution of a procedure. */
467 /* Makes all preparations for reading from the data source and writing
470 open_active_file (void)
472 /* Sometimes we want to refer to the dictionary that applies to the
473 data actually written to the sink. This is either temp_dict or
474 default_dict. However, if TEMPORARY is not on, then temp_dict
475 does not apply. So, we can set temp_dict to default_dict in this
480 temp_dict = default_dict;
483 /* No cases passed to the procedure yet. */
487 prepare_for_writing ();
488 arrange_compaction ();
490 vector_initialization ();
491 discard_ctl_stack ();
496 debug_printf (("vfm: reading from %s source, writing to %s sink.\n",
497 vfm_source->name, vfm_sink->name));
498 debug_printf (("vfm: vfm_source_info.nval=%d, vfm_sink_info.nval=%d, "
499 "temp_dict->nval=%d, compaction_nval=%d, "
500 "default_dict.nval=%d\n",
501 vfm_source_info.nval, vfm_sink_info.nval, temp_dict->nval,
502 compaction_nval, default_dict.nval));
505 /* Closes the active file. */
507 close_active_file (struct write_case_data *data)
509 /* Close the current case group. */
510 if (case_count && data->endfunc != NULL)
511 data->endfunc (data->aux);
513 /* Stop lagging (catch up?). */
518 for (i = 0; i < n_lag; i++)
524 /* Assume the dictionary from right before TEMPORARY, if any. Turn
528 dict_destroy (default_dict);
529 default_dict = temp_dict;
533 /* Finish compaction. */
534 if (compaction_necessary)
535 finish_compaction ();
537 /* Old data sink --> New data source. */
538 if (vfm_source && vfm_source->destroy_source)
539 vfm_source->destroy_source ();
541 vfm_source = vfm_sink;
542 vfm_source_info.ncases = vfm_sink_info.ncases;
543 vfm_source_info.nval = compaction_nval;
544 vfm_source_info.case_size = (sizeof (struct ccase)
545 + (compaction_nval - 1) * sizeof (union value));
546 if (vfm_source->mode)
549 /* Old data sink is gone now. */
552 /* Cancel TEMPORARY. */
555 /* Free temporary cases. */
559 free (compaction_case);
560 compaction_case = NULL;
562 /* Cancel PROCESS IF. */
563 expr_free (process_if_expr);
564 process_if_expr = NULL;
566 /* Cancel FILTER if temporary. */
567 if (filter_var != NULL && !FILTER_before_TEMPORARY)
568 dict_set_filter (default_dict, NULL);
570 /* Cancel transformations. */
571 cancel_transformations ();
573 /* Turn off case limiter. */
574 dict_set_case_limit (default_dict, 0);
576 /* Clear VECTOR vectors. */
577 dict_clear_vectors (default_dict);
579 debug_printf (("vfm: procedure complete\n\n"));
582 /* Disk case stream. */
584 /* Associated files. */
585 FILE *disk_source_file;
586 FILE *disk_sink_file;
588 /* Initializes the disk sink. */
590 disk_stream_init (void)
592 disk_sink_file = tmpfile ();
595 msg (ME, _("An error occurred attempting to create a temporary "
596 "file for use as the active file: %s."),
602 /* Reads all cases from the disk source and passes them one by one to
605 disk_stream_read (write_case_func *write_case, write_case_data wc_data)
609 for (i = 0; i < vfm_source_info.ncases; i++)
611 if (!fread (temp_case, vfm_source_info.case_size, 1, disk_source_file))
613 msg (ME, _("An error occurred while attempting to read from "
614 "a temporary file created for the active file: %s."),
620 if (!write_case (wc_data))
625 /* Writes temp_case to the disk sink. */
627 disk_stream_write (void)
629 union value *src_case;
631 if (compaction_necessary)
633 compact_case (compaction_case, temp_case);
634 src_case = (union value *) compaction_case;
636 else src_case = (union value *) temp_case;
638 if (fwrite (src_case, sizeof *src_case * compaction_nval, 1,
639 disk_sink_file) != 1)
641 msg (ME, _("An error occurred while attempting to write to a "
642 "temporary file used as the active file: %s."),
648 /* Switches the stream from a sink to a source. */
650 disk_stream_mode (void)
652 /* Rewind the sink. */
653 if (fseek (disk_sink_file, 0, SEEK_SET) != 0)
655 msg (ME, _("An error occurred while attempting to rewind a "
656 "temporary file used as the active file: %s."),
661 /* Sink --> source variables. */
662 disk_source_file = disk_sink_file;
665 /* Destroys the source's internal data. */
667 disk_stream_destroy_source (void)
669 if (disk_source_file)
671 fclose (disk_source_file);
672 disk_source_file = NULL;
676 /* Destroys the sink's internal data. */
678 disk_stream_destroy_sink (void)
682 fclose (disk_sink_file);
683 disk_sink_file = NULL;
688 struct case_stream vfm_disk_stream =
694 disk_stream_destroy_source,
695 disk_stream_destroy_sink,
699 /* Memory case stream. */
701 /* List of cases stored in the stream. */
702 struct case_list *memory_source_cases;
703 struct case_list *memory_sink_cases;
706 struct case_list *memory_sink_iter;
708 /* Maximum number of cases. */
709 int memory_sink_max_cases;
711 /* Initializes the memory stream variables for writing. */
713 memory_stream_init (void)
715 memory_sink_cases = NULL;
716 memory_sink_iter = NULL;
718 assert (compaction_nval);
719 memory_sink_max_cases = MAX_WORKSPACE / (sizeof (union value) * compaction_nval);
722 /* Reads the case stream from memory and passes it to write_case(). */
724 memory_stream_read (write_case_func *write_case, write_case_data wc_data)
726 while (memory_source_cases != NULL)
728 memcpy (temp_case, &memory_source_cases->c, vfm_source_info.case_size);
731 struct case_list *current = memory_source_cases;
732 memory_source_cases = memory_source_cases->next;
736 if (!write_case (wc_data))
741 /* Writes temp_case to the memory stream. */
743 memory_stream_write (void)
745 struct case_list *new_case = malloc (sizeof (struct case_list)
746 + ((compaction_nval - 1)
747 * sizeof (union value)));
749 /* If we've got memory to spare then add it to the linked list. */
750 if (vfm_sink_info.ncases <= memory_sink_max_cases && new_case != NULL)
752 if (compaction_necessary)
753 compact_case (&new_case->c, temp_case);
755 memcpy (&new_case->c, temp_case, sizeof (union value) * compaction_nval);
757 /* Append case to linked list. */
758 if (memory_sink_cases)
759 memory_sink_iter = memory_sink_iter->next = new_case;
761 memory_sink_iter = memory_sink_cases = new_case;
765 /* Out of memory. Write the active file to disk. */
766 struct case_list *cur, *next;
768 /* Notify the user. */
770 msg (MW, _("Virtual memory exhausted. Paging active file "
773 msg (MW, _("Workspace limit of %d KB (%d cases at %d bytes each) "
774 "overflowed. Paging active file to disk."),
775 MAX_WORKSPACE / 1024, memory_sink_max_cases,
776 compaction_nval * sizeof (union value));
780 /* Switch to a disk sink. */
781 vfm_sink = &vfm_disk_stream;
785 /* Terminate the list. */
786 if (memory_sink_iter)
787 memory_sink_iter->next = NULL;
789 /* Write the cases to disk and destroy them. We can't call
790 vfm->sink->write() because of compaction. */
791 for (cur = memory_sink_cases; cur; cur = next)
794 if (fwrite (cur->c.data, sizeof (union value) * compaction_nval, 1,
795 disk_sink_file) != 1)
797 msg (ME, _("An error occurred while attempting to "
798 "write to a temporary file created as the "
799 "active file, while paging to disk: %s."),
806 /* Write the current case to disk. */
811 /* If the data is stored in memory, causes it to be written to disk.
812 To be called only *between* procedure()s, not within them. */
816 if (vfm_source == &vfm_memory_stream)
818 /* Switch to a disk sink. */
819 vfm_sink = &vfm_disk_stream;
823 /* Write the cases to disk and destroy them. We can't call
824 vfm->sink->write() because of compaction. */
826 struct case_list *cur, *next;
828 for (cur = memory_source_cases; cur; cur = next)
831 if (fwrite (cur->c.data, sizeof *cur->c.data * compaction_nval, 1,
832 disk_sink_file) != 1)
834 msg (ME, _("An error occurred while attempting to "
835 "write to a temporary file created as the "
836 "active file, while paging to disk: %s."),
844 vfm_source = &vfm_disk_stream;
851 /* Switch the memory stream from sink to source mode. */
853 memory_stream_mode (void)
855 /* Terminate the list. */
856 if (memory_sink_iter)
857 memory_sink_iter->next = NULL;
859 /* Sink --> source variables. */
860 memory_source_cases = memory_sink_cases;
861 memory_sink_cases = NULL;
864 /* Destroy all memory source data. */
866 memory_stream_destroy_source (void)
868 struct case_list *cur, *next;
870 for (cur = memory_source_cases; cur; cur = next)
875 memory_source_cases = NULL;
878 /* Destroy all memory sink data. */
880 memory_stream_destroy_sink (void)
882 struct case_list *cur, *next;
884 for (cur = memory_sink_cases; cur; cur = next)
889 memory_sink_cases = NULL;
893 struct case_stream vfm_memory_stream =
899 memory_stream_destroy_source,
900 memory_stream_destroy_sink,
904 #include "debug-print.h"
906 /* Add temp_case to the lag queue. */
910 if (lag_count < n_lag)
912 memcpy (lag_queue[lag_head], temp_case,
913 sizeof (union value) * dict_get_value_cnt (temp_dict));
914 if (++lag_head >= n_lag)
918 /* Returns a pointer to the lagged case from N_BEFORE cases before the
919 current one, or NULL if there haven't been that many cases yet. */
921 lagged_case (int n_before)
923 assert (n_before <= n_lag);
924 if (n_before > lag_count)
928 int index = lag_head - n_before;
931 return lag_queue[index];
935 /* Transforms temp_case and writes it to the replacement active file
936 if advisable. Returns nonzero if more cases can be accepted, zero
937 otherwise. Do not call this function again after it has returned
940 procedure_write_case (write_case_data wc_data)
942 /* Index of current transformation. */
945 /* Return value: whether it's reasonable to write any more cases. */
948 debug_printf ((_("transform: ")));
953 /* Output the case if this is temp_trns. */
954 if (cur_trns == temp_trns)
956 debug_printf (("REC"));
961 vfm_sink_info.ncases++;
964 if (dict_get_case_limit (default_dict))
965 more_cases = (vfm_sink_info.ncases
966 < dict_get_case_limit (default_dict));
970 if (cur_trns >= n_trns)
973 debug_printf (("$%d", cur_trns));
975 /* Decide which transformation should come next. */
979 code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case);
983 /* Next transformation. */
987 /* Delete this case. */
990 /* Go to that transformation. */
997 /* Call the beginning of group function. */
998 if (!case_count && wc_data->beginfunc != NULL)
999 wc_data->beginfunc (wc_data->aux);
1001 /* Call the procedure if there is one and FILTER and PROCESS IF
1002 don't prohibit it. */
1003 if (wc_data->procfunc != NULL
1005 && (process_if_expr == NULL ||
1006 expr_evaluate (process_if_expr, temp_case, NULL) == 1.0))
1007 wc_data->procfunc (temp_case, wc_data->aux);
1012 debug_putc ('\n', stdout);
1016 /* Return previously determined value. */
1020 /* Clears the variables in the temporary case that need to be
1021 cleared between processing cases. */
1023 clear_temp_case (void)
1025 /* FIXME? This is linear in the number of variables, but
1026 doesn't need to be, so it's an easy optimization target. */
1027 size_t var_cnt = dict_get_var_cnt (default_dict);
1030 for (i = 0; i < var_cnt; i++)
1032 struct variable *v = dict_get_var (default_dict, i);
1033 if (v->init && v->reinit)
1035 if (v->type == NUMERIC)
1036 temp_case->data[v->fv].f = SYSMIS;
1038 memset (temp_case->data[v->fv].s, ' ', v->width);
1043 /* Appends TRNS to t_trns[], the list of all transformations to be
1044 performed on data as it is read from the active file. */
1046 add_transformation (struct trns_header * trns)
1048 if (n_trns >= m_trns)
1051 t_trns = xrealloc (t_trns, sizeof *t_trns * m_trns);
1053 t_trns[n_trns] = trns;
1054 trns->index = n_trns++;
1057 /* Cancels all active transformations, including any transformations
1058 created by the input program. */
1060 cancel_transformations (void)
1063 for (i = 0; i < n_trns; i++)
1065 if (t_trns[i]->free)
1066 t_trns[i]->free (t_trns[i]);
1069 n_trns = f_trns = 0;
1077 /* Dumps out the values of all the split variables for the case C. */
1079 dump_splits (struct ccase *c)
1081 struct variable *const *split;
1082 struct tab_table *t;
1086 split_cnt = dict_get_split_cnt (default_dict);
1087 t = tab_create (3, split_cnt + 1, 0);
1088 tab_dim (t, tab_natural_dimensions);
1089 tab_vline (t, TAL_1 | TAL_SPACING, 1, 0, split_cnt);
1090 tab_vline (t, TAL_1 | TAL_SPACING, 2, 0, split_cnt);
1091 tab_text (t, 0, 0, TAB_NONE, _("Variable"));
1092 tab_text (t, 1, 0, TAB_LEFT, _("Value"));
1093 tab_text (t, 2, 0, TAB_LEFT, _("Label"));
1094 split = dict_get_split_vars (default_dict);
1095 for (i = 0; i < split_cnt; i++)
1097 struct variable *v = split[i];
1099 const char *val_lab;
1101 assert (v->type == NUMERIC || v->type == ALPHA);
1102 tab_text (t, 0, i + 1, TAB_LEFT | TAT_PRINTF, "%s", v->name);
1105 union value val = c->data[v->fv];
1106 if (v->type == ALPHA)
1107 val.c = c->data[v->fv].s;
1108 data_out (temp_buf, &v->print, &val);
1111 temp_buf[v->print.w] = 0;
1112 tab_text (t, 1, i + 1, TAT_PRINTF, "%.*s", v->print.w, temp_buf);
1114 val_lab = val_labs_find (v->val_labs, c->data[v->fv]);
1116 tab_text (t, 2, i + 1, TAB_LEFT, val_lab);
1118 tab_flags (t, SOMF_NO_TITLE);
1122 /* This procfunc is substituted for the user-supplied procfunc when
1123 SPLIT FILE is active. This function forms a wrapper around that
1124 procfunc by dividing the input into series. */
1126 SPLIT_FILE_procfunc (struct ccase *c, void *data_)
1128 struct write_case_data *data = data_;
1129 static struct ccase *prev_case;
1130 struct variable *const *split;
1134 /* The first case always begins a new series. We also need to
1135 preserve the values of the case for later comparison. */
1136 if (case_count == 0)
1140 prev_case = xmalloc (vfm_sink_info.case_size);
1141 memcpy (prev_case, c, vfm_sink_info.case_size);
1144 if (data->beginfunc != NULL)
1145 data->beginfunc (data->aux);
1147 return data->procfunc (c, data->aux);
1150 /* Compare the value of each SPLIT FILE variable to the values on
1151 the previous case. */
1152 split = dict_get_split_vars (default_dict);
1153 split_cnt = dict_get_split_cnt (default_dict);
1154 for (i = 0; i < split_cnt; i++)
1156 struct variable *v = split[i];
1161 if (approx_ne (c->data[v->fv].f, prev_case->data[v->fv].f))
1165 if (memcmp (c->data[v->fv].s, prev_case->data[v->fv].s, v->width))
1172 return data->procfunc (c, data->aux);
1175 /* The values of the SPLIT FILE variable are different from the
1176 values on the previous case. That means that it's time to begin
1178 if (data->endfunc != NULL)
1179 data->endfunc (data->aux);
1181 if (data->beginfunc != NULL)
1182 data->beginfunc (data->aux);
1183 memcpy (prev_case, c, vfm_sink_info.case_size);
1184 return data->procfunc (c, data->aux);
1187 /* Case compaction. */
1189 /* Copies case SRC to case DEST, compacting it in the process. */
1191 compact_case (struct ccase *dest, const struct ccase *src)
1197 assert (compaction_necessary);
1201 if (dest != compaction_case)
1202 memcpy (dest, compaction_case, sizeof (union value) * compaction_nval);
1206 /* Copy all the variables except the scratch variables from SRC to
1208 var_cnt = dict_get_var_cnt (default_dict);
1209 for (i = 0; i < var_cnt; i++)
1211 struct variable *v = dict_get_var (default_dict, i);
1213 if (v->name[0] == '#')
1216 if (v->type == NUMERIC)
1217 dest->data[nval++] = src->data[v->fv];
1220 int w = DIV_RND_UP (v->width, sizeof (union value));
1222 memcpy (&dest->data[nval], &src->data[v->fv], w * sizeof (union value));
1228 /* Reassigns `fv' for each variable. Deletes scratch variables. */
1230 finish_compaction (void)
1234 for (i = 0; i < dict_get_var_cnt (default_dict); )
1236 struct variable *v = dict_get_var (default_dict, i);
1238 if (v->name[0] == '#')
1239 dict_delete_var (default_dict, v);
1243 dict_compact_values (default_dict);