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 static int recursive_call;
131 struct write_case_data procedure_write_data;
132 struct write_case_data split_file_data;
134 assert (++recursive_call == 1);
136 if (dict_get_split_cnt (default_dict) == 0)
138 /* Normally we just use the data passed by the user. */
139 procedure_write_data.beginfunc = beginfunc;
140 procedure_write_data.procfunc = procfunc;
141 procedure_write_data.endfunc = endfunc;
142 procedure_write_data.aux = aux;
146 /* Under SPLIT FILE, we add a layer of indirection. */
147 procedure_write_data.beginfunc = NULL;
148 procedure_write_data.procfunc = SPLIT_FILE_procfunc;
149 procedure_write_data.endfunc = endfunc;
150 procedure_write_data.aux = &split_file_data;
152 split_file_data.beginfunc = beginfunc;
153 split_file_data.procfunc = procfunc;
154 split_file_data.endfunc = endfunc;
155 split_file_data.aux = aux;
158 last_vfm_invocation = time (NULL);
161 vfm_source->read (procedure_write_case, &procedure_write_data);
162 close_active_file (&procedure_write_data);
164 assert (--recursive_call == 0);
167 /* Active file processing support. Subtly different semantics from
170 static int process_active_file_write_case (struct write_case_data *data);
172 /* The casefunc might want us to stop calling it. */
173 static int not_canceled;
175 /* Reads all the cases from the active file and passes them one-by-one
176 to CASEFUNC in temp_case. Before any cases are passed, calls
177 BEGINFUNC. After all the cases have been passed, calls ENDFUNC.
178 BEGINFUNC, CASEFUNC, and ENDFUNC can write temp_case to the output
179 file by calling process_active_file_output_case().
181 process_active_file() ignores TEMPORARY, SPLIT FILE, and N. */
183 process_active_file (void (*beginfunc) (void *),
184 int (*casefunc) (struct ccase *curcase, void *),
185 void (*endfunc) (void *),
188 struct write_case_data process_active_write_data;
190 process_active_write_data.beginfunc = beginfunc;
191 process_active_write_data.procfunc = casefunc;
192 process_active_write_data.endfunc = endfunc;
193 process_active_write_data.aux = aux;
200 /* There doesn't necessarily need to be an active file. */
202 vfm_source->read (process_active_file_write_case,
203 &process_active_write_data);
206 close_active_file (&process_active_write_data);
209 /* Pass the current case to casefunc. */
211 process_active_file_write_case (struct write_case_data *data)
213 /* Index of current transformation. */
216 for (cur_trns = f_trns ; cur_trns != temp_trns; )
220 code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case);
224 /* Next transformation. */
228 /* Delete this case. */
231 /* Go to that transformation. */
240 /* Call the procedure if FILTER and PROCESS IF don't prohibit it. */
241 if (not_canceled && !exclude_this_case ())
242 not_canceled = data->procfunc (temp_case, data->aux);
252 /* Write temp_case to the active file. */
254 process_active_file_output_case (void)
256 vfm_sink_info.ncases++;
260 /* Opening the active file. */
262 /* It might be usefully noted that the following several functions are
263 given in the order that they are called by open_active_file(). */
265 /* Prepare to write to the replacement active file. */
267 prepare_for_writing (void)
269 /* FIXME: If ALL the conditions listed below hold true, then the
270 replacement active file is guaranteed to be identical to the
271 original active file:
273 1. TEMPORARY was the first transformation, OR, there were no
274 transformations at all.
276 2. Input is not coming from an input program.
278 3. Compaction is not necessary.
280 So, in this case, we shouldn't have to replace the active
281 file--it's just a waste of time and space. */
283 vfm_sink_info.ncases = 0;
284 vfm_sink_info.nval = dict_get_next_value_idx (default_dict);
285 vfm_sink_info.case_size = dict_get_case_size (default_dict);
287 if (vfm_sink == NULL)
289 if (vfm_sink_info.case_size * vfm_source_info.ncases > MAX_WORKSPACE
292 msg (MW, _("Workspace overflow predicted. Max workspace is "
293 "currently set to %d KB (%d cases at %d bytes each). "
294 "Paging active file to disk."),
295 MAX_WORKSPACE / 1024, MAX_WORKSPACE / vfm_sink_info.case_size,
296 vfm_sink_info.case_size);
301 vfm_sink = paging ? &vfm_disk_stream : &vfm_memory_stream;
305 /* Arrange for compacting the output cases for storage. */
307 arrange_compaction (void)
309 int count_values = 0;
314 /* Count up the number of `value's that will be output. */
315 for (i = 0; i < dict_get_var_cnt (temp_dict); i++)
317 struct variable *v = dict_get_var (temp_dict, i);
319 if (v->name[0] != '#')
322 count_values += v->nv;
325 assert (temporary == 2
326 || count_values <= dict_get_next_value_idx (temp_dict));
329 /* Compaction is only necessary if the number of `value's to output
330 differs from the number already present. */
331 compaction_nval = count_values;
332 if (temporary == 2 || count_values != dict_get_next_value_idx (temp_dict))
333 compaction_necessary = 1;
335 compaction_necessary = 0;
341 /* Prepares the temporary case and compaction case. */
343 make_temp_case (void)
345 temp_case = xmalloc (vfm_sink_info.case_size);
347 if (compaction_necessary)
348 compaction_case = xmalloc (sizeof (struct ccase)
349 + sizeof (union value) * (compaction_nval - 1));
353 /* Returns the name of the variable that owns the index CCASE_INDEX
356 index_to_varname (int ccase_index)
360 for (i = 0; i < default_dict.nvar; i++)
362 struct variable *v = default_dict.var[i];
364 if (ccase_index >= v->fv && ccase_index < v->fv + v->nv)
365 return default_dict.var[i]->name;
371 /* Initializes temp_case from the vectors that say which `value's
372 need to be initialized just once, and which ones need to be
373 re-initialized before every case. */
375 vector_initialization (void)
377 size_t var_cnt = dict_get_var_cnt (default_dict);
380 for (i = 0; i < var_cnt; i++)
382 struct variable *v = dict_get_var (default_dict, i);
384 if (v->type == NUMERIC)
387 temp_case->data[v->fv].f = 0.0;
389 temp_case->data[v->fv].f = SYSMIS;
392 memset (temp_case->data[v->fv].s, ' ', v->width);
396 /* Sets all the lag-related variables based on value of n_lag. */
407 lag_queue = xmalloc (n_lag * sizeof *lag_queue);
408 for (i = 0; i < n_lag; i++)
409 lag_queue[i] = xmalloc (dict_get_case_size (temp_dict));
412 /* There is a lot of potential confusion in the vfm and related
413 routines over the number of `value's at each stage of the process.
414 Here is each nval count, with explanation, as set up by
417 vfm_source_info.nval: Number of `value's in the cases returned by
418 the source stream. This value turns out not to be very useful, but
419 we maintain it anyway.
421 vfm_sink_info.nval: Number of `value's in the cases after all
422 transformations have been performed. Never less than
423 vfm_source_info.nval.
425 temp_dict->nval: Number of `value's in the cases after the
426 transformations leading up to TEMPORARY have been performed. If
427 TEMPORARY was not specified, this is equal to vfm_sink_info.nval.
428 Never less than vfm_sink_info.nval.
430 compaction_nval: Number of `value's in the cases after the
431 transformations leading up to TEMPORARY have been performed and the
432 case has been compacted by compact_case(), if compaction is
433 necessary. This the number of `value's in the cases saved by the
434 sink stream. (However, note that the cases passed to the sink
435 stream have not yet been compacted. It is the responsibility of
436 the data sink to call compact_case().) This may be less than,
437 greater than, or equal to vfm_source_info.nval. `compaction'
438 becomes the new value of default_dict.nval after the procedure is
441 default_dict.nval: This is often an alias for temp_dict->nval. As
442 such it can really have no separate existence until the procedure
443 is complete. For this reason it should *not* be referenced inside
444 the execution of a procedure. */
445 /* Makes all preparations for reading from the data source and writing
448 open_active_file (void)
450 /* Sometimes we want to refer to the dictionary that applies to the
451 data actually written to the sink. This is either temp_dict or
452 default_dict. However, if TEMPORARY is not on, then temp_dict
453 does not apply. So, we can set temp_dict to default_dict in this
458 temp_dict = default_dict;
461 /* No cases passed to the procedure yet. */
465 prepare_for_writing ();
466 arrange_compaction ();
468 vector_initialization ();
469 discard_ctl_stack ();
473 debug_printf (("vfm: reading from %s source, writing to %s sink.\n",
474 vfm_source->name, vfm_sink->name));
475 debug_printf (("vfm: vfm_source_info.nval=%d, vfm_sink_info.nval=%d, "
476 "temp_dict->nval=%d, compaction_nval=%d, "
477 "default_dict.nval=%d\n",
478 vfm_source_info.nval, vfm_sink_info.nval, temp_dict->nval,
479 compaction_nval, default_dict.nval));
482 /* Closes the active file. */
484 close_active_file (struct write_case_data *data)
486 /* Close the current case group. */
487 if (case_count && data->endfunc != NULL)
488 data->endfunc (data->aux);
490 /* Stop lagging (catch up?). */
495 for (i = 0; i < n_lag; i++)
501 /* Assume the dictionary from right before TEMPORARY, if any. Turn
505 dict_destroy (default_dict);
506 default_dict = temp_dict;
510 /* Finish compaction. */
511 if (compaction_necessary)
512 finish_compaction ();
514 /* Old data sink --> New data source. */
515 if (vfm_source && vfm_source->destroy_source)
516 vfm_source->destroy_source ();
518 vfm_source = vfm_sink;
519 vfm_source_info.ncases = vfm_sink_info.ncases;
520 vfm_source_info.nval = compaction_nval;
521 vfm_source_info.case_size = (sizeof (struct ccase)
522 + (compaction_nval - 1) * sizeof (union value));
523 if (vfm_source->mode)
526 /* Old data sink is gone now. */
529 /* Cancel TEMPORARY. */
532 /* Free temporary cases. */
536 free (compaction_case);
537 compaction_case = NULL;
539 /* Cancel PROCESS IF. */
540 expr_free (process_if_expr);
541 process_if_expr = NULL;
543 /* Cancel FILTER if temporary. */
544 if (dict_get_filter (default_dict) != NULL && !FILTER_before_TEMPORARY)
545 dict_set_filter (default_dict, NULL);
547 /* Cancel transformations. */
548 cancel_transformations ();
550 /* Turn off case limiter. */
551 dict_set_case_limit (default_dict, 0);
553 /* Clear VECTOR vectors. */
554 dict_clear_vectors (default_dict);
556 debug_printf (("vfm: procedure complete\n\n"));
559 /* Disk case stream. */
561 /* Associated files. */
562 FILE *disk_source_file;
563 FILE *disk_sink_file;
565 /* Initializes the disk sink. */
567 disk_stream_init (void)
569 disk_sink_file = tmpfile ();
572 msg (ME, _("An error occurred attempting to create a temporary "
573 "file for use as the active file: %s."),
579 /* Reads all cases from the disk source and passes them one by one to
582 disk_stream_read (write_case_func *write_case, write_case_data wc_data)
586 for (i = 0; i < vfm_source_info.ncases; i++)
588 if (!fread (temp_case, vfm_source_info.case_size, 1, disk_source_file))
590 msg (ME, _("An error occurred while attempting to read from "
591 "a temporary file created for the active file: %s."),
597 if (!write_case (wc_data))
602 /* Writes temp_case to the disk sink. */
604 disk_stream_write (void)
606 union value *src_case;
608 if (compaction_necessary)
610 compact_case (compaction_case, temp_case);
611 src_case = (union value *) compaction_case;
613 else src_case = (union value *) temp_case;
615 if (fwrite (src_case, sizeof *src_case * compaction_nval, 1,
616 disk_sink_file) != 1)
618 msg (ME, _("An error occurred while attempting to write to a "
619 "temporary file used as the active file: %s."),
625 /* Switches the stream from a sink to a source. */
627 disk_stream_mode (void)
629 /* Rewind the sink. */
630 if (fseek (disk_sink_file, 0, SEEK_SET) != 0)
632 msg (ME, _("An error occurred while attempting to rewind a "
633 "temporary file used as the active file: %s."),
638 /* Sink --> source variables. */
639 disk_source_file = disk_sink_file;
642 /* Destroys the source's internal data. */
644 disk_stream_destroy_source (void)
646 if (disk_source_file)
648 fclose (disk_source_file);
649 disk_source_file = NULL;
653 /* Destroys the sink's internal data. */
655 disk_stream_destroy_sink (void)
659 fclose (disk_sink_file);
660 disk_sink_file = NULL;
665 struct case_stream vfm_disk_stream =
671 disk_stream_destroy_source,
672 disk_stream_destroy_sink,
676 /* Memory case stream. */
678 /* List of cases stored in the stream. */
679 struct case_list *memory_source_cases;
680 struct case_list *memory_sink_cases;
683 struct case_list *memory_sink_iter;
685 /* Maximum number of cases. */
686 int memory_sink_max_cases;
688 /* Initializes the memory stream variables for writing. */
690 memory_stream_init (void)
692 memory_sink_cases = NULL;
693 memory_sink_iter = NULL;
695 assert (compaction_nval);
696 memory_sink_max_cases = MAX_WORKSPACE / (sizeof (union value) * compaction_nval);
699 /* Reads the case stream from memory and passes it to write_case(). */
701 memory_stream_read (write_case_func *write_case, write_case_data wc_data)
703 while (memory_source_cases != NULL)
705 memcpy (temp_case, &memory_source_cases->c, vfm_source_info.case_size);
708 struct case_list *current = memory_source_cases;
709 memory_source_cases = memory_source_cases->next;
713 if (!write_case (wc_data))
718 /* Writes temp_case to the memory stream. */
720 memory_stream_write (void)
722 struct case_list *new_case = malloc (sizeof (struct case_list)
723 + ((compaction_nval - 1)
724 * sizeof (union value)));
726 /* If we've got memory to spare then add it to the linked list. */
727 if (vfm_sink_info.ncases <= memory_sink_max_cases && new_case != NULL)
729 if (compaction_necessary)
730 compact_case (&new_case->c, temp_case);
732 memcpy (&new_case->c, temp_case, sizeof (union value) * compaction_nval);
734 /* Append case to linked list. */
735 if (memory_sink_cases)
736 memory_sink_iter = memory_sink_iter->next = new_case;
738 memory_sink_iter = memory_sink_cases = new_case;
742 /* Out of memory. Write the active file to disk. */
743 struct case_list *cur, *next;
745 /* Notify the user. */
747 msg (MW, _("Virtual memory exhausted. Paging active file "
750 msg (MW, _("Workspace limit of %d KB (%d cases at %d bytes each) "
751 "overflowed. Paging active file to disk."),
752 MAX_WORKSPACE / 1024, memory_sink_max_cases,
753 compaction_nval * sizeof (union value));
757 /* Switch to a disk sink. */
758 vfm_sink = &vfm_disk_stream;
762 /* Terminate the list. */
763 if (memory_sink_iter)
764 memory_sink_iter->next = NULL;
766 /* Write the cases to disk and destroy them. We can't call
767 vfm->sink->write() because of compaction. */
768 for (cur = memory_sink_cases; cur; cur = next)
771 if (fwrite (cur->c.data, sizeof (union value) * compaction_nval, 1,
772 disk_sink_file) != 1)
774 msg (ME, _("An error occurred while attempting to "
775 "write to a temporary file created as the "
776 "active file, while paging to disk: %s."),
783 /* Write the current case to disk. */
788 /* If the data is stored in memory, causes it to be written to disk.
789 To be called only *between* procedure()s, not within them. */
793 if (vfm_source == &vfm_memory_stream)
795 /* Switch to a disk sink. */
796 vfm_sink = &vfm_disk_stream;
800 /* Write the cases to disk and destroy them. We can't call
801 vfm->sink->write() because of compaction. */
803 struct case_list *cur, *next;
805 for (cur = memory_source_cases; cur; cur = next)
808 if (fwrite (cur->c.data, sizeof *cur->c.data * compaction_nval, 1,
809 disk_sink_file) != 1)
811 msg (ME, _("An error occurred while attempting to "
812 "write to a temporary file created as the "
813 "active file, while paging to disk: %s."),
821 vfm_source = &vfm_disk_stream;
828 /* Switch the memory stream from sink to source mode. */
830 memory_stream_mode (void)
832 /* Terminate the list. */
833 if (memory_sink_iter)
834 memory_sink_iter->next = NULL;
836 /* Sink --> source variables. */
837 memory_source_cases = memory_sink_cases;
838 memory_sink_cases = NULL;
841 /* Destroy all memory source data. */
843 memory_stream_destroy_source (void)
845 struct case_list *cur, *next;
847 for (cur = memory_source_cases; cur; cur = next)
852 memory_source_cases = NULL;
855 /* Destroy all memory sink data. */
857 memory_stream_destroy_sink (void)
859 struct case_list *cur, *next;
861 for (cur = memory_sink_cases; cur; cur = next)
866 memory_sink_cases = NULL;
870 struct case_stream vfm_memory_stream =
876 memory_stream_destroy_source,
877 memory_stream_destroy_sink,
881 #include "debug-print.h"
883 /* Add temp_case to the lag queue. */
887 if (lag_count < n_lag)
889 memcpy (lag_queue[lag_head], temp_case,
890 dict_get_case_size (temp_dict));
891 if (++lag_head >= n_lag)
895 /* Returns a pointer to the lagged case from N_BEFORE cases before the
896 current one, or NULL if there haven't been that many cases yet. */
898 lagged_case (int n_before)
900 assert (n_before <= n_lag);
901 if (n_before > lag_count)
905 int index = lag_head - n_before;
908 return lag_queue[index];
912 /* Transforms temp_case and writes it to the replacement active file
913 if advisable. Returns nonzero if more cases can be accepted, zero
914 otherwise. Do not call this function again after it has returned
917 procedure_write_case (write_case_data wc_data)
919 /* Index of current transformation. */
922 /* Return value: whether it's reasonable to write any more cases. */
925 debug_printf ((_("transform: ")));
930 /* Output the case if this is temp_trns. */
931 if (cur_trns == temp_trns)
933 debug_printf (("REC"));
938 vfm_sink_info.ncases++;
941 if (dict_get_case_limit (default_dict))
942 more_cases = (vfm_sink_info.ncases
943 < dict_get_case_limit (default_dict));
947 if (cur_trns >= n_trns)
950 debug_printf (("$%d", cur_trns));
952 /* Decide which transformation should come next. */
956 code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case);
960 /* Next transformation. */
964 /* Delete this case. */
967 /* Go to that transformation. */
974 /* Call the beginning of group function. */
975 if (!case_count && wc_data->beginfunc != NULL)
976 wc_data->beginfunc (wc_data->aux);
978 /* Call the procedure if there is one and FILTER and PROCESS IF
979 don't prohibit it. */
980 if (wc_data->procfunc != NULL && !exclude_this_case ())
981 wc_data->procfunc (temp_case, wc_data->aux);
986 debug_putc ('\n', stdout);
990 /* Return previously determined value. */
994 /* Clears the variables in the temporary case that need to be
995 cleared between processing cases. */
997 clear_temp_case (void)
999 /* FIXME? This is linear in the number of variables, but
1000 doesn't need to be, so it's an easy optimization target. */
1001 size_t var_cnt = dict_get_var_cnt (default_dict);
1004 for (i = 0; i < var_cnt; i++)
1006 struct variable *v = dict_get_var (default_dict, i);
1007 if (v->init && v->reinit)
1009 if (v->type == NUMERIC)
1010 temp_case->data[v->fv].f = SYSMIS;
1012 memset (temp_case->data[v->fv].s, ' ', v->width);
1017 /* Returns nonzero if this case should be exclude as specified on
1018 FILTER or PROCESS IF, otherwise zero. */
1020 exclude_this_case (void)
1023 struct variable *filter_var = dict_get_filter (default_dict);
1024 if (filter_var != NULL)
1026 double f = temp_case->data[filter_var->fv].f;
1027 if (f == 0.0 || f == SYSMIS || is_num_user_missing (f, filter_var))
1032 if (process_if_expr != NULL
1033 && expr_evaluate (process_if_expr, temp_case, NULL) != 1.0)
1039 /* Appends TRNS to t_trns[], the list of all transformations to be
1040 performed on data as it is read from the active file. */
1042 add_transformation (struct trns_header * trns)
1044 if (n_trns >= m_trns)
1047 t_trns = xrealloc (t_trns, sizeof *t_trns * m_trns);
1049 t_trns[n_trns] = trns;
1050 trns->index = n_trns++;
1053 /* Cancels all active transformations, including any transformations
1054 created by the input program. */
1056 cancel_transformations (void)
1059 for (i = 0; i < n_trns; i++)
1061 if (t_trns[i]->free)
1062 t_trns[i]->free (t_trns[i]);
1065 n_trns = f_trns = 0;
1073 /* Dumps out the values of all the split variables for the case C. */
1075 dump_splits (struct ccase *c)
1077 struct variable *const *split;
1078 struct tab_table *t;
1082 split_cnt = dict_get_split_cnt (default_dict);
1083 t = tab_create (3, split_cnt + 1, 0);
1084 tab_dim (t, tab_natural_dimensions);
1085 tab_vline (t, TAL_1 | TAL_SPACING, 1, 0, split_cnt);
1086 tab_vline (t, TAL_1 | TAL_SPACING, 2, 0, split_cnt);
1087 tab_text (t, 0, 0, TAB_NONE, _("Variable"));
1088 tab_text (t, 1, 0, TAB_LEFT, _("Value"));
1089 tab_text (t, 2, 0, TAB_LEFT, _("Label"));
1090 split = dict_get_split_vars (default_dict);
1091 for (i = 0; i < split_cnt; i++)
1093 struct variable *v = split[i];
1095 const char *val_lab;
1097 assert (v->type == NUMERIC || v->type == ALPHA);
1098 tab_text (t, 0, i + 1, TAB_LEFT | TAT_PRINTF, "%s", v->name);
1100 data_out (temp_buf, &v->print, &c->data[v->fv]);
1102 temp_buf[v->print.w] = 0;
1103 tab_text (t, 1, i + 1, TAT_PRINTF, "%.*s", v->print.w, temp_buf);
1105 val_lab = val_labs_find (v->val_labs, c->data[v->fv]);
1107 tab_text (t, 2, i + 1, TAB_LEFT, val_lab);
1109 tab_flags (t, SOMF_NO_TITLE);
1113 /* This procfunc is substituted for the user-supplied procfunc when
1114 SPLIT FILE is active. This function forms a wrapper around that
1115 procfunc by dividing the input into series. */
1117 SPLIT_FILE_procfunc (struct ccase *c, void *data_)
1119 struct write_case_data *data = data_;
1120 static struct ccase *prev_case;
1121 struct variable *const *split;
1125 /* The first case always begins a new series. We also need to
1126 preserve the values of the case for later comparison. */
1127 if (case_count == 0)
1131 prev_case = xmalloc (vfm_sink_info.case_size);
1132 memcpy (prev_case, c, vfm_sink_info.case_size);
1135 if (data->beginfunc != NULL)
1136 data->beginfunc (data->aux);
1138 return data->procfunc (c, data->aux);
1141 /* Compare the value of each SPLIT FILE variable to the values on
1142 the previous case. */
1143 split = dict_get_split_vars (default_dict);
1144 split_cnt = dict_get_split_cnt (default_dict);
1145 for (i = 0; i < split_cnt; i++)
1147 struct variable *v = split[i];
1152 if (approx_ne (c->data[v->fv].f, prev_case->data[v->fv].f))
1156 if (memcmp (c->data[v->fv].s, prev_case->data[v->fv].s, v->width))
1163 return data->procfunc (c, data->aux);
1166 /* The values of the SPLIT FILE variable are different from the
1167 values on the previous case. That means that it's time to begin
1169 if (data->endfunc != NULL)
1170 data->endfunc (data->aux);
1172 if (data->beginfunc != NULL)
1173 data->beginfunc (data->aux);
1174 memcpy (prev_case, c, vfm_sink_info.case_size);
1175 return data->procfunc (c, data->aux);
1178 /* Case compaction. */
1180 /* Copies case SRC to case DEST, compacting it in the process. */
1182 compact_case (struct ccase *dest, const struct ccase *src)
1188 assert (compaction_necessary);
1192 if (dest != compaction_case)
1193 memcpy (dest, compaction_case, sizeof (union value) * compaction_nval);
1197 /* Copy all the variables except the scratch variables from SRC to
1199 var_cnt = dict_get_var_cnt (default_dict);
1200 for (i = 0; i < var_cnt; i++)
1202 struct variable *v = dict_get_var (default_dict, i);
1204 if (v->name[0] == '#')
1207 if (v->type == NUMERIC)
1208 dest->data[nval++] = src->data[v->fv];
1211 int w = DIV_RND_UP (v->width, sizeof (union value));
1213 memcpy (&dest->data[nval], &src->data[v->fv], w * sizeof (union value));
1219 /* Reassigns `fv' for each variable. Deletes scratch variables. */
1221 finish_compaction (void)
1225 for (i = 0; i < dict_get_var_cnt (default_dict); )
1227 struct variable *v = dict_get_var (default_dict, i);
1229 if (v->name[0] == '#')
1230 dict_delete_var (default_dict, v);
1234 dict_compact_values (default_dict);