+Wed Mar  3 09:30:09 2004  Ben Pfaff  <blp@gnu.org>
+
+       * main.c: (main) sigaction()'s sa_flags member was uninitialized.
+       Just use signal() instead.
+
+Wed Mar  3 09:26:30 2004  Ben Pfaff  <blp@gnu.org>
+
+       Get rid of vfm_sink_info and vfm_source_info.
+       
+       * aggregate.c: (agr_00x_end_func) Don't increment
+       sfm_sink_info.ncases.
+
+       * sort.c: (do_internal_sort) Get case count from
+       vfm_source->class->count().
+       (struct external_sort) Add `case_size' member.
+       (do_external_sort) Initialize case_size.
+       (struct initial_run_state) Add `case_size' member.
+       (write_initial_runs) Initialize case_size.
+       (sort_sink_write) Use case_size.
+       (read_external_sort_output) Use case_size.  Get case_cnt from
+       initial_runs.
+
+       * vfm.c: (struct write_case_data) Add underscores to existing arg
+       names, all references updated.  Renamed `aux' as `func_aux', all
+       references updated.  Added new `aux' member.
+       (global var vfm_source_info) Removed.
+       (global var vfm_sink_info) Removed.
+       (struct procedure_aux_data) New.
+       (struct split_aux_data) New.
+       (procedure) Use `aux' fields for procedure_aux_data,
+       split_aux_data.
+       (process_active_file_write_case) Pass case_count + 1 to
+       transformation procedures, exclude_this_case().
+       (process_active_file_output_case) Don't increment
+       vfm_sink_info.ncases.
+       (prepare_for_writing) Don't initialize vfm_sink_info.  Don't try
+       to send data to disk early.
+       (make_temp_case) Don't use vfm_sink_info.case_size.
+       (close_active_file) Don't initialize vfm_source_info.
+       (struct disk_stream_info) New, to allow for case_cnt and case_size fields.
+       (disk_sink_create) Initialize and/or update disk_stream_info.
+       (disk_sink_write) Ditto.
+       (disk_sink_destroy) Ditto.
+       (disk_sink_make_source) Ditto.
+       (disk_source_read) Ditto.
+       (disk_source_destroy) Ditto.
+       (global var disk_source_class) Add disk_source_count().
+       (disk_source_count) New function.
+       (struct memory_sink_info) Add `case_cnt', `case_size' members.
+       (struct memory_source_info) Ditto.
+       (memory_sink_create) Deal with case_cnt, case_size.
+       (memory_sink_write) Ditto.
+       (memory_sink_make_source) Ditto.
+       (memory_source_read) Ditto.
+       (memory_source_count) New function.
+       (memory_source_class) Add memory_source_count().
+       (procedure_write_case) Don't use vfm_sink_info.ncases.  Do use
+       proc_aux->cases_written, and pass it to transformation procedures
+       and exclude_this_case ().
+       (exclude_this_case) Add case_num parameter.  Pass it to
+       expr_evaluate().
+       (SPLIT_FILE_procfunc) Use split_aux->prev_case instead of static
+       variable.
+
+       * vfm.h: (struct case_source_class) Add `count' member.
+
+       * vfmP.h: (struct stream_info) Removed.
+       (global variable vfm_source_info) Removed.
+       (global variable vfm_sink_info) Removed.
+       
+Tue Mar  2 23:38:17 2004  Ben Pfaff  <blp@gnu.org>
+
+       * var.h: (typedef trns_proc_func) New typedef.
+       (trns_free_func) New typedef.
+       (struct trns_header) Change `proc' to type trns_proc_func, `free'
+       to type trns_free_func.  This only changes the actual type of
+       trns_proc_func, adding a `case_num' parameter.  Updated all
+       implementations to use the typedefs instead.
+
+       * compute.c: (compute_num) Pass case_num to expr_evaluate().
+       (compute_num_vec) Ditto.
+       (compute_str) Ditto.
+       (compute_str_vec) Ditto.
+
+       * do-if.c: (do_if_trns_proc) Ditto.
+
+       * expr-evl.c: (expr_evaluate) Add new case_num parameter, use for
+       OP_CASENUM.
+
+       * inpt-pgm.c: (input_program_source_read) Maintain case count,
+       pass to transformation functions.
+       (reread_trns_proc) Pass case_num arg to expr_evaluate().
+
+       * loop.c: (loop_1_trns_proc) Ditto.
+       (loop_2_trns_proc) Ditto.
+       (loop_3_trns_proc) Ditto.
+
+       * print.c: (print_space_trns_proc) Ditto.
+
+       * sel-if.c: (select_if_proc) Ditto.
+
 Tue Mar  2 11:36:52 2004  Ben Pfaff  <blp@gnu.org>
 
        * frequencies.q: (cleanup_freq_tab) Avoid memory leak by
 
 static int aggregate_single_case (struct ccase *input, struct ccase *output);
 static int create_sysfile (void);
 
-static int agr_00x_trns_proc (struct trns_header *, struct ccase *);
-static void agr_00x_end_func (void *);
-static int agr_10x_trns_proc (struct trns_header *, struct ccase *);
-static void agr_10x_trns_free (struct trns_header *);
+static trns_proc_func agr_00x_trns_proc, agr_10x_trns_proc;
+static trns_free_func agr_10x_trns_free;
+static void agr_00x_end_func (void *aux);
 static void agr_10x_end_func (void *);
 static int agr_11x_func (write_case_data);
 
 /* Aggregate each case as it comes through.  Cases which aren't needed
    are dropped. */
 static int
-agr_00x_trns_proc (struct trns_header *h UNUSED, struct ccase *c)
+agr_00x_trns_proc (struct trns_header *h UNUSED, struct ccase *c,
+                   int case_num UNUSED)
 {
   int code = aggregate_single_case (c, compaction_case);
   debug_printf (("%d ", code));
   /* Ensure that info for the last break group gets written to the
      active file. */
   dump_aggregate_info (compaction_case);
-  vfm_sink_info.ncases++;
   vfm_sink->class->write (vfm_sink, temp_case);
 }
 
 /* Aggregate the current case and output it if we passed a
    breakpoint. */
 static int
-agr_10x_trns_proc (struct trns_header *h UNUSED, struct ccase *c)
+agr_10x_trns_proc (struct trns_header *h UNUSED, struct ccase *c,
+                   int case_num UNUSED)
 {
   int code = aggregate_single_case (c, buf_1xx);
 
 
-#ifndef SORT_ALGO_H
-#define SORT_ALGO_H 1
+#ifndef ALGORITHM_H
+#define ALGORITHM_H 1
 
 #include <stddef.h>
 
              algo_compare_func *compare, void *aux);
 
 
-#endif /* sort-algo.h */
+#endif /* algorithm.h */
 
 static int descend;
 static int print;
 
-static int autorecode_trns_proc (struct trns_header *, struct ccase *);
-static void autorecode_trns_free (struct trns_header *);
+static trns_proc_func autorecode_trns_proc;
+static trns_free_func autorecode_trns_free;
 static int autorecode_proc_func (struct ccase *, void *);
 static hsh_compare_func compare_alpha_value, compare_numeric_value;
 static hsh_hash_func hash_alpha_value, hash_numeric_value;
 }
 
 static int
-autorecode_trns_proc (struct trns_header * trns, struct ccase * c)
+autorecode_trns_proc (struct trns_header * trns, struct ccase * c,
+                      int case_num UNUSED)
 {
   struct autorecode_trns *t = (struct autorecode_trns *) trns;
   int i;
 
 /* Transformation functions. */
 
 static int
-compute_num (struct trns_header *compute_, struct ccase *c)
+compute_num (struct trns_header *compute_, struct ccase *c,
+             int case_num)
 {
   struct compute_trns *compute = (struct compute_trns *) compute_;
 
   if (compute->test == NULL
-      || expr_evaluate (compute->test, c, NULL) == 1.0) 
+      || expr_evaluate (compute->test, c, case_num, NULL) == 1.0) 
     {
-      expr_evaluate (compute->rvalue, c, &c->data[compute->fv]); 
+      expr_evaluate (compute->rvalue, c, case_num, &c->data[compute->fv]); 
     }
   
   return -1;
 }
 
 static int
-compute_num_vec (struct trns_header *compute_, struct ccase *c)
+compute_num_vec (struct trns_header *compute_, struct ccase *c,
+                 int case_num)
 {
   struct compute_trns *compute = (struct compute_trns *) compute_;
 
   if (compute->test == NULL
-      || expr_evaluate (compute->test, c, NULL) == 1.0) 
+      || expr_evaluate (compute->test, c, case_num, NULL) == 1.0) 
     {
       /* Index into the vector. */
       union value index;
       /* Rounded index value. */
       int rindx;
 
-      expr_evaluate (compute->element, c, &index);
+      expr_evaluate (compute->element, c, case_num, &index);
       rindx = floor (index.f + EPSILON);
       if (index.f == SYSMIS || rindx < 1 || rindx > compute->vector->cnt)
         {
                  index.f, compute->vector->name);
           return -1;
         }
-      expr_evaluate (compute->rvalue, c,
+      expr_evaluate (compute->rvalue, c, case_num,
                      &c->data[compute->vector->var[rindx - 1]->fv]); 
     }
   
 }
 
 static int
-compute_str (struct trns_header *compute_, struct ccase *c)
+compute_str (struct trns_header *compute_, struct ccase *c,
+             int case_num)
 {
   struct compute_trns *compute = (struct compute_trns *) compute_;
 
   if (compute->test == NULL
-      || expr_evaluate (compute->test, c, NULL) == 1.0) 
+      || expr_evaluate (compute->test, c, case_num, NULL) == 1.0) 
     {
       /* Temporary storage for string expression return value. */
       union value v;
 
-      expr_evaluate (compute->rvalue, c, &v);
+      expr_evaluate (compute->rvalue, c, case_num, &v);
       st_bare_pad_len_copy (c->data[compute->fv].s, &v.c[1], compute->width,
                             v.c[0]); 
     }
 }
 
 static int
-compute_str_vec (struct trns_header *compute_, struct ccase *c)
+compute_str_vec (struct trns_header *compute_, struct ccase *c,
+                 int case_num)
 {
   struct compute_trns *compute = (struct compute_trns *) compute_;
 
   if (compute->test == NULL
-      || expr_evaluate (compute->test, c, NULL) == 1.0) 
+      || expr_evaluate (compute->test, c, case_num, NULL) == 1.0) 
     {
       /* Temporary storage for string expression return value. */
       union value v;
       /* Variable reference by indexed vector. */
       struct variable *vr;
 
-      expr_evaluate (compute->element, c, &index);
+      expr_evaluate (compute->element, c, case_num, &index);
       rindx = floor (index.f + EPSILON);
       if (index.f == SYSMIS || rindx < 1 || rindx > compute->vector->cnt)
         {
           return -1;
         }
 
-      expr_evaluate (compute->rvalue, c, &v);
+      expr_evaluate (compute->rvalue, c, case_num, &v);
       vr = compute->vector->var[rindx - 1];
       st_bare_pad_len_copy (c->data[vr->fv].s, &v.c[1], vr->width, v.c[0]); 
     }
 
 \f
 /* Parser. */
 
-static int count_trns_proc (struct trns_header *, struct ccase *);
-static void count_trns_free (struct trns_header *);
+static trns_proc_func count_trns_proc;
+static trns_free_func count_trns_free;
 
 static int parse_numeric_criteria (struct counting *);
 static int parse_string_criteria (struct counting *);
 
 /* Performs the COUNT transformation T on case C. */
 static int
-count_trns_proc (struct trns_header * trns, struct ccase * c)
+count_trns_proc (struct trns_header * trns, struct ccase * c,
+                 int case_num UNUSED)
 {
   struct cnt_var_info *info;
   struct counting *cnt;
 
                               const struct file_handle *handle, int nrec);
 static void dump_free_table (const struct data_list_pgm *);
 static void destroy_dls_var_spec (struct dls_var_spec *);
-static void destroy_dls (struct trns_header *);
-static int read_one_case (struct trns_header *, struct ccase *);
+static trns_free_func destroy_dls;
+static trns_proc_func read_one_case;
 
 /* Message title for REPEATING DATA. */
 #define RPD_ERR "REPEATING DATA: "
 /* Note that since this is exclusively an input program, C is
    guaranteed to be temp_case. */
 static int
-read_one_case (struct trns_header *t, struct ccase *c UNUSED)
+read_one_case (struct trns_header *t, struct ccase *c UNUSED,
+               int case_num UNUSED)
 {
   struct data_list_pgm *dls = (struct data_list_pgm *) t;
   data_list_read_func *read_func;
 const struct case_source_class data_list_source_class = 
   {
     "DATA LIST",
+    NULL,
     data_list_source_read,
     data_list_source_destroy,
   };
     write_case_data wc_data;
   };
 
-int repeating_data_trns_proc (struct trns_header *, struct ccase *);
-void repeating_data_trns_free (struct trns_header *);
+static trns_free_func repeating_data_trns_free;
 static int parse_num_or_var (struct rpd_num_or_var *, const char *);
 static int parse_repeating_data (struct dls_var_spec **,
                                  struct dls_var_spec **);
    elements in the REPEATING DATA structure.  Returns -1 on success,
    -2 on end of file or on failure. */
 int
-repeating_data_trns_proc (struct trns_header *trns, struct ccase *c)
+repeating_data_trns_proc (struct trns_header *trns, struct ccase *c,
+                          int case_num UNUSED)
 {
   struct repeating_data_trns *t = (struct repeating_data_trns *) trns;
     
 
 #include "var.h"
 #include "vfm.h"
 
-int repeating_data_trns_proc (struct trns_header *, struct ccase *);
+trns_proc_func repeating_data_trns_proc;
 void repeating_data_set_write_case (struct trns_header *,
                                     write_case_func *, write_case_data);
 
 
 
 /* Transformation function to calculate Z-scores. */
 static int
-descriptives_trns_proc (struct trns_header * trns, struct ccase * c)
+descriptives_trns_proc (struct trns_header * trns, struct ccase * c,
+                        int case_num UNUSED)
 {
   struct descriptives_trns *t = (struct descriptives_trns *) trns;
   struct dsc_z_score *z;
 
 
 static struct do_if_trns *parse_do_if (void);
 static void add_ELSE_IF (struct do_if_trns *);
-static int goto_trns_proc (struct trns_header *, struct ccase *);
-static int do_if_trns_proc (struct trns_header *, struct ccase *);
-static void do_if_trns_free (struct trns_header *);
+static trns_proc_func goto_trns_proc, do_if_trns_proc;
+static trns_free_func do_if_trns_free;
 
 /* Parse DO IF. */
 int
 
 /* Executes a goto transformation. */
 static int 
-goto_trns_proc (struct trns_header * t, struct ccase * c UNUSED)
+goto_trns_proc (struct trns_header * t, struct ccase * c UNUSED,
+                int case_num UNUSED)
 {
   return ((struct goto_trns *) t)->dest;
 }
 
 static int 
-do_if_trns_proc (struct trns_header * trns, struct ccase * c)
+do_if_trns_proc (struct trns_header * trns, struct ccase * c,
+                 int case_num UNUSED)
 {
   struct do_if_trns *t = (struct do_if_trns *) trns;
   union value bool;
 
-  expr_evaluate (t->cond, c, &bool);
+  expr_evaluate (t->cond, c, case_num, &bool);
   if (bool.f == 1.0)
     {
       debug_printf ((_("DO IF %d: true\n"), t->h.index));
 
 #include "vfmP.h"
 
 double
-expr_evaluate (struct expression *e, struct ccase *c, union value *v)
+expr_evaluate (struct expression *e, struct ccase *c, int case_num,
+               union value *v)
 {
   unsigned char *op = e->op;
   double *dbl = e->num;
          break;
        case OP_CASENUM:
          sp++;
-         sp->f = vfm_sink_info.ncases + 1;
+         sp->f = case_num;
          break;
 
        case OP_SENTINEL:
 
 union value;
 
 struct expression *expr_parse (int flags);
-double expr_evaluate (struct expression *, struct ccase *, union value *);
+double expr_evaluate (struct expression *, struct ccase *, int case_num,
+                      union value *);
 void expr_free (struct expression *);
 
 #endif /* expr.h */
 
 const struct case_source_class file_type_source_class =
   {
     "FILE TYPE",
+    NULL,
     file_type_source_read,
     file_type_source_destroy,
   };
 
 static const struct case_source_class flip_source_class = 
   {
     "FLIP",
+    NULL,
     flip_source_read,
     flip_source_destroy
   };
 
 
 static int trim_dictionary (struct dictionary * dict, int *options);
 static int save_write_case_func (struct ccase *, void *);
-static int save_trns_proc (struct trns_header *, struct ccase *);
-static void save_trns_free (struct trns_header *);
+static trns_proc_func save_trns_proc;
+static trns_free_func save_trns_free;
 
 #if DEBUGGING
 void dump_dict_variables (struct dictionary *);
 }
 
 static int
-save_trns_proc (struct trns_header *h, struct ccase * c)
+save_trns_proc (struct trns_header *h, struct ccase * c, int case_num UNUSED)
 {
   struct save_trns *t = (struct save_trns *) h;
   do_write_case (t, c);
 const struct case_source_class get_source_class =
   {
     "GET",
+    NULL,
     get_source_read,
     get_source_destroy,
   };
 const struct case_source_class import_source_class =
   {
     "IMPORT",
+    NULL,
     import_source_read,
     get_source_destroy,
   };
 
     size_t init_cnt;            /* Number of elements in inp_init. */
   };
 
-static int end_case_trns_proc (struct trns_header *, struct ccase *);
-static int end_file_trns_proc (struct trns_header * t, struct ccase * c);
-static int reread_trns_proc (struct trns_header *, struct ccase *);
-static void reread_trns_free (struct trns_header *);
+static trns_proc_func end_case_trns_proc, reread_trns_proc, end_file_trns_proc;
+static trns_free_func reread_trns_free;
 
 int
 cmd_input_program (void)
      cases. */
   int end_case = 0;
 
+  /* FIXME?  This is the number of cases sent out of the input
+     program, not the number of cases written to the procedure.
+     The difference should only show up in $CASENUM in COMPUTE.
+     We should check behavior against SPSS. */
+  int cases_written = 0;
+
   assert (inp != NULL);
   
   /* Figure end_case. */
 
           if (t_trns[i]->proc == end_case_trns_proc) 
             {
+              cases_written++;
               if (!write_case (wc_data))
                 return;
               clear_case (inp);
               continue;
             }
 
-         code = t_trns[i]->proc (t_trns[i], temp_case);
+         code = t_trns[i]->proc (t_trns[i], temp_case, cases_written + 1);
          switch (code)
            {
            case -1:
 const struct case_source_class input_program_source_class =
   {
     "INPUT PROGRAM",
+    NULL,
     input_program_source_read,
     input_program_source_destroy,
   };
 }
 
 int
-end_case_trns_proc (struct trns_header *t UNUSED, struct ccase * c UNUSED)
+end_case_trns_proc (struct trns_header *t UNUSED, struct ccase * c UNUSED,
+                    int case_num UNUSED)
 {
   assert (0);
 }
 }
 
 static int
-reread_trns_proc (struct trns_header * pt, struct ccase * c)
+reread_trns_proc (struct trns_header * pt, struct ccase * c,
+                  int case_num)
 {
   struct reread_trns *t = (struct reread_trns *) pt;
 
     {
       union value column;
 
-      expr_evaluate (t->column, c, &column);
+      expr_evaluate (t->column, c, case_num, &column);
       if (!finite (column.f) || column.f < 1)
        {
          msg (SE, _("REREAD: Column numbers must be positive finite "
 }
 
 static int
-end_file_trns_proc (struct trns_header * t UNUSED, struct ccase * c UNUSED)
+end_file_trns_proc (struct trns_header * t UNUSED, struct ccase * c UNUSED,
+                    int case_num UNUSED)
 {
 #if DEBUGGING
   printf ("END FILE\n");
 
 
 static int internal_cmd_loop (void);
 static int internal_cmd_end_loop (void);
-static int break_trns_proc (struct trns_header *, struct ccase *);
-static int loop_1_trns_proc (struct trns_header *, struct ccase *);
-static void loop_1_trns_free (struct trns_header *);
-static int loop_2_trns_proc (struct trns_header *, struct ccase *);
-static void loop_2_trns_free (struct trns_header *);
-static int loop_3_trns_proc (struct trns_header *, struct ccase *);
-static void loop_3_trns_free (struct trns_header *);
+static trns_proc_func break_trns_proc;
+static trns_proc_func loop_1_trns_proc, loop_2_trns_proc, loop_3_trns_proc;
+static trns_free_func loop_1_trns_free, loop_2_trns_free, loop_3_trns_free;
 static void pop_ctl_stack (void);
 \f
 /* LOOP. */
 
 /* Performs transformation 1. */
 static int
-loop_1_trns_proc (struct trns_header * trns, struct ccase * c)
+loop_1_trns_proc (struct trns_header * trns, struct ccase * c,
+                  int case_num)
 {
   struct loop_1_trns *one = (struct loop_1_trns *) trns;
   struct loop_2_trns *two = one->two;
     {
       union value t1, t2, t3;
 
-      expr_evaluate (one->init, c, &t1);
+      expr_evaluate (one->init, c, case_num, &t1);
       if (one->incr)
-       expr_evaluate (one->incr, c, &t2);
+       expr_evaluate (one->incr, c, case_num, &t2);
       else
        t2.f = 1.0;
-      expr_evaluate (one->term, c, &t3);
+      expr_evaluate (one->term, c, case_num, &t3);
 
       /* Even if the loop is never entered, force the index variable
          to assume the initial value. */
 
 /* Performs transformation 2. */
 static int
-loop_2_trns_proc (struct trns_header * trns, struct ccase * c)
+loop_2_trns_proc (struct trns_header * trns, struct ccase * c,
+                  int case_num UNUSED)
 {
   struct loop_2_trns *two = (struct loop_2_trns *) trns;
 
 
   /* Conditional clause limiter. */
   if ((two->flags & LPC_COND)
-      && expr_evaluate (two->cond, c, NULL) != 1.0)
+      && expr_evaluate (two->cond, c, case_num, NULL) != 1.0)
     return two->loop_term;
 
   return -1;
 
 /* Performs transformation 3. */
 static int
-loop_3_trns_proc (struct trns_header * trns, struct ccase * c)
+loop_3_trns_proc (struct trns_header * trns, struct ccase * c,
+                  int case_num)
 {
   struct loop_3_trns *thr = (struct loop_3_trns *) trns;
 
   /* Note that it breaks out of the loop if the expression is true *or
      missing*.  This is conformant. */
-  if (thr->cond && expr_evaluate (two->cond, c, NULL) != 0.0)
+  if (thr->cond && expr_evaluate (two->cond, c, case_num, NULL) != 0.0)
     return -1;
 
   return thr->loop_start;
 }
 
 static int
-break_trns_proc (struct trns_header * trns, struct ccase * c UNUSED)
+break_trns_proc (struct trns_header * trns, struct ccase * c UNUSED,
+                 int case_num UNUSED)
 {
   return ((struct break_trns *) trns)->loop_term;
 }
 
 int
 main (int argc, char **argv)
 {
-  struct sigaction bug ;
-  bug.sa_handler = bug_handler;
-
-  sigaction(SIGSEGV, &bug,0);
+  signal (SIGSEGV, bug_handler);
 
   /* Initialization. */
   if (!outp_init ())
 
 static const struct case_source_class matrix_data_with_rowtype_source_class = 
   {
     "MATRIX DATA",
+    NULL,
     matrix_data_read_with_rowtype,
     NULL,
   };
 matrix_data_without_rowtype_source_class =
   {
     "MATRIX DATA",
+    NULL,
     matrix_data_read_without_rowtype,
     NULL,
   };
 
 static int nrec;
 
 static int internal_cmd_print (int flags);
-static int print_trns_proc (struct trns_header *, struct ccase *);
-static void print_trns_free (struct trns_header *);
+static trns_proc_func print_trns_proc;
+static trns_free_func print_trns_free;
 static int parse_specs (void);
 static void dump_table (void);
 static void append_var_spec (struct prt_out_spec *spec);
 
 /* Performs the transformation inside print_trns T on case C. */
 static int
-print_trns_proc (struct trns_header * trns, struct ccase * c)
+print_trns_proc (struct trns_header * trns, struct ccase * c,
+                 int case_num UNUSED)
 {
   /* Transformation. */
   struct print_trns *t = (struct print_trns *) trns;
 }
 print_space_trns;
 
-static int print_space_trns_proc (struct trns_header *, struct ccase *);
-static void print_space_trns_free (struct trns_header *);
+static trns_proc_func print_space_trns_proc;
+static trns_free_func print_space_trns_free;
 
 int
 cmd_print_space (void)
 }
 
 static int
-print_space_trns_proc (struct trns_header * trns, struct ccase * c)
+print_space_trns_proc (struct trns_header * trns, struct ccase * c,
+                       int case_num UNUSED)
 {
   struct print_space_trns *t = (struct print_space_trns *) trns;
   int n;
     {
       union value v;
 
-      expr_evaluate (t->e, c, &v);
+      expr_evaluate (t->e, c, case_num, &v);
       n = v.f;
       if (n < 0)
        {
 
 static int parse_dest_spec (struct rcd_var * rcd, union value *v,
                            size_t *max_dst_width);
 static int parse_src_spec (struct rcd_var * rcd, int type, size_t max_src_width);
-static int recode_trns_proc (struct trns_header *, struct ccase *);
-static void recode_trns_free (struct trns_header *);
+static trns_proc_func recode_trns_proc;
+static trns_free_func recode_trns_free;
 static double convert_to_double (char *, int);
 
 #if DEBUGGING
 }
 
 static int
-recode_trns_proc (struct trns_header * t, struct ccase * c)
+recode_trns_proc (struct trns_header * t, struct ccase * c,
+                  int case_num UNUSED)
 {
   struct rcd_var *v;
   struct coding *cp;
 
     unsigned frac;              /* TYPE_FRACTION: a fraction of UINT_MAX. */
   };
 
-int sample_trns_proc (struct trns_header *, struct ccase *);
+static trns_proc_func sample_trns_proc;
 
 int
 cmd_sample (void)
   return lex_end_of_command ();
 }
 
-int
-sample_trns_proc (struct trns_header * trns, struct ccase *c UNUSED)
+static int
+sample_trns_proc (struct trns_header * trns, struct ccase *c UNUSED,
+                  int case_num UNUSED)
 {
   struct sample_trns *t = (struct sample_trns *) trns;
   double U;
 
     struct expression *e;      /* Test expression. */
   };
 
-static int select_if_proc (struct trns_header *, struct ccase *);
-static void select_if_free (struct trns_header *);
+static trns_proc_func select_if_proc;
+static trns_free_func select_if_free;
 
 /* Parses the SELECT IF transformation. */
 int
 
 /* Performs the SELECT IF transformation T on case C. */
 static int
-select_if_proc (struct trns_header * t, struct ccase * c)
+select_if_proc (struct trns_header * t, struct ccase * c,
+                int case_num)
 {
-  return (expr_evaluate (((struct select_if_trns *) t)->e, c, NULL) == 1.0) - 2;
+  return (expr_evaluate (((struct select_if_trns *) t)->e, c,
+                         case_num, NULL) == 1.0) - 2;
 }
 
 /* Frees SELECT IF transformation T. */
 
        {
           struct case_list *case_list;
           struct case_list **case_array;
-          size_t case_cnt;
+          int case_cnt;
           int i;
 
-          case_cnt = vfm_source_info.ncases;
-         if (case_cnt == 0)
+          case_cnt = vfm_source->class->count (vfm_source);
+         if (case_cnt <= 0)
             return isrt;
 
           if (case_cnt > set_max_workspace / sizeof *case_array)
     size_t run_cnt, run_cap;          /* Number of runs, allocated capacity. */
     char *temp_dir;                   /* Temporary file directory name. */
     int next_file_idx;                /* Lowest unused file index. */
+    size_t case_size;                 /* Number of bytes in case. */
   };
 
 /* Prototypes for helper functions. */
 
   xsrt = xmalloc (sizeof *xsrt);
   xsrt->scp = scp;
+  xsrt->case_size = sizeof (union value) * compaction_nval;
   if (!init_external_sort (xsrt))
     goto done;
   if (!write_initial_runs (xsrt, separate))
     /* Run currently being output. */
     int file_idx;               /* Temporary file number. */
     size_t case_cnt;            /* Number of cases so far. */
+    size_t case_size;           /* Number of bytes in a case. */
     FILE *output_file;          /* Output file. */
     struct case_list *last_output;/* Record last output. */
 
   irs->last_output = NULL;
   irs->file_idx = 0;
   irs->case_cnt = 0;
+  irs->case_size = dict_get_case_size (default_dict);
   irs->okay = 1;
   if (!allocate_cases (irs)) 
     goto done;
   assert (irs->record_cnt < irs->record_cap);
   new_record_run = irs->records + irs->record_cnt++;
   new_record_run->record = grab_case (irs);
-  memcpy (new_record_run->record->c.data, c->data, vfm_sink_info.case_size);
+  memcpy (new_record_run->record->c.data, c->data, irs->case_size);
   new_record_run->run = irs->file_idx;
   if (irs->last_output != NULL
       && compare_record (c->data, irs->last_output->c.data,
 {
   FILE *file;
   int file_idx;
-  int i;
+  size_t i;
 
   assert (xsrt->run_cnt == 1);
   file_idx = xsrt->initial_runs[0].file_idx;
       return;
     }
 
-  for (i = 0; i < vfm_source_info.ncases; i++)
+  for (i = 0; i < xsrt->initial_runs[0].case_cnt; i++)
     {
       if (!read_temp_file (xsrt, file_idx, file,
-                          temp_case, vfm_source_info.case_size))
+                          temp_case, xsrt->case_size))
        {
           err_failure ();
           break;
 const struct case_source_class sort_source_class =
   {
     "SORT CASES",
+    NULL, /* FIXME */
     sort_source_read,
     sort_source_destroy,
   };
 
 \f
 /* Transformations. */
 
+struct trns_header;
+typedef int trns_proc_func (struct trns_header *, struct ccase *, int);
+typedef void trns_free_func (struct trns_header *);
+
 /* Header for all transformations. */
 struct trns_header
   {
-    /* Index into t_trns[]. */
-    int index;
-
-    /* Transformation proc. */
-    int (*proc) (struct trns_header *, struct ccase *);
-
-    /* Garbage collector proc. */
-    void (*free) (struct trns_header *);
+    int index;                  /* Index into t_trns[]. */
+    trns_proc_func *proc;       /* Transformation proc. */
+    trns_free_func *free;       /* Garbage collector proc. */
   };
 
 /* Array of transformations */
 
    then deleted and the data sink becomes the data source for the
    next procedure. */
 
-#include "debug-print.h"
-
 /* Procedure execution data. */
 struct write_case_data
   {
-    void (*beginfunc) (void *);
-    int (*procfunc) (struct ccase *, void *);
-    void (*endfunc) (void *);
+    /* Functions to call... */
+    void (*begin_func) (void *);               /* ...before data. */
+    int (*proc_func) (struct ccase *, void *); /* ...with data. */
+    void (*end_func) (void *);                 /* ...after data. */
+    void *func_aux;                            /* Auxiliary data. */ 
+
+    /* Extra auxiliary data. */
     void *aux;
   };
 
 /* The replacement active file, to which cases are written. */
 struct case_sink *vfm_sink;
 
-/* Information about the data source. */
-struct stream_info vfm_source_info;
-
-/* Information about the data sink. */
-struct stream_info vfm_sink_info;
-
 /* Nonzero if the case needs to have values deleted before being
    stored, zero otherwise. */
 int compaction_necessary;
 
-/* Number of values after compaction, or the same as
-   vfm_sink_info.nval, if compaction is not necessary. */
+/* Number of values after compaction. */
 int compaction_nval;
 
 /* Temporary case buffer with enough room for `compaction_nval'
 
 static void open_active_file (void);
 static void close_active_file (struct write_case_data *);
-static int SPLIT_FILE_procfunc (struct ccase *, void *);
+static int SPLIT_FILE_proc_func (struct ccase *, void *);
 static void finish_compaction (void);
 static void lag_case (void);
 static int procedure_write_case (struct write_case_data *);
 static void clear_temp_case (void);
-static int exclude_this_case (void);
+static int exclude_this_case (int case_num);
 \f
 /* Public functions. */
 
+struct procedure_aux_data 
+  {
+    size_t cases_written;       /* Number of cases written so far. */
+  };
+
+struct split_aux_data 
+  {
+    struct ccase *prev_case;    /* Data in previous case. */
+  };
+
 /* Reads all the cases from the active file, transforms them by
-   the active set of transformations, calls PROCFUNC with CURCASE
+   the active set of transformations, calls PROC_FUNC with CURCASE
    set to the case, and writes them to a new active file.
 
    Divides the active file into zero or more series of one or more
-   cases each.  BEGINFUNC is called before each series.  ENDFUNC is
+   cases each.  BEGIN_FUNC is called before each series.  END_FUNC is
    called after each series.
 
-   Arbitrary user-specified data AUX is passed to BEGINFUNC,
-   PROCFUNC, and ENDFUNC as auxiliary data. */
+   Arbitrary user-specified data AUX is passed to BEGIN_FUNC,
+   PROC_FUNC, and END_FUNC as auxiliary data. */
 void
-procedure (void (*beginfunc) (void *),
-          int (*procfunc) (struct ccase *curcase, void *),
-          void (*endfunc) (void *),
-           void *aux)
+procedure (void (*begin_func) (void *),
+          int (*proc_func) (struct ccase *curcase, void *),
+          void (*end_func) (void *),
+           void *func_aux)
 {
   static int recursive_call;
 
   struct write_case_data procedure_write_data;
+  struct procedure_aux_data proc_aux;
+
   struct write_case_data split_file_data;
+  struct split_aux_data split_aux;
+  int split;
 
   assert (++recursive_call == 1);
 
-  if (dict_get_split_cnt (default_dict) == 0) 
-    {
-      /* Normally we just use the data passed by the user. */
-      procedure_write_data.beginfunc = beginfunc;
-      procedure_write_data.procfunc = procfunc;
-      procedure_write_data.endfunc = endfunc;
-      procedure_write_data.aux = aux;
-    }
-  else
+  proc_aux.cases_written = 0;
+
+  /* Normally we just use the data passed by the user. */
+  procedure_write_data.begin_func = begin_func;
+  procedure_write_data.proc_func = proc_func;
+  procedure_write_data.end_func = end_func;
+  procedure_write_data.func_aux = func_aux;
+  procedure_write_data.aux = &proc_aux;
+
+  /* Under SPLIT FILE, we add a layer of indirection. */
+  split = dict_get_split_cnt (default_dict) > 0;
+  if (split) 
     {
-      /* Under SPLIT FILE, we add a layer of indirection. */
-      procedure_write_data.beginfunc = NULL;
-      procedure_write_data.procfunc = SPLIT_FILE_procfunc;
-      procedure_write_data.endfunc = endfunc;
-      procedure_write_data.aux = &split_file_data;
-
-      split_file_data.beginfunc = beginfunc;
-      split_file_data.procfunc = procfunc;
-      split_file_data.endfunc = endfunc;
-      split_file_data.aux = aux;
+      split_file_data = procedure_write_data;
+      split_file_data.aux = &split_aux;
+
+      split_aux.prev_case = xmalloc (dict_get_case_size (default_dict));
+
+      procedure_write_data.begin_func = NULL;
+      procedure_write_data.proc_func = SPLIT_FILE_proc_func;
+      procedure_write_data.end_func = end_func;
+      procedure_write_data.func_aux = &split_file_data;
     }
 
   last_vfm_invocation = time (NULL);
                              procedure_write_case, &procedure_write_data);
   close_active_file (&procedure_write_data);
 
+  if (split)
+    free (split_aux.prev_case);
+
   assert (--recursive_call == 0);
 }
 \f
 
 /* Reads all the cases from the active file and passes them one-by-one
    to CASEFUNC in temp_case.  Before any cases are passed, calls
-   BEGINFUNC.  After all the cases have been passed, calls ENDFUNC.
-   BEGINFUNC, CASEFUNC, and ENDFUNC can write temp_case to the output
+   BEGIN_FUNC.  After all the cases have been passed, calls END_FUNC.
+   BEGIN_FUNC, CASEFUNC, and END_FUNC can write temp_case to the output
    file by calling process_active_file_output_case().
 
    process_active_file() ignores TEMPORARY, SPLIT FILE, and N. */
 void
-process_active_file (void (*beginfunc) (void *),
+process_active_file (void (*begin_func) (void *),
                     int (*casefunc) (struct ccase *curcase, void *),
-                    void (*endfunc) (void *),
-                     void *aux)
+                    void (*end_func) (void *),
+                     void *func_aux)
 {
   struct write_case_data process_active_write_data;
 
-  process_active_write_data.beginfunc = beginfunc;
-  process_active_write_data.procfunc = casefunc;
-  process_active_write_data.endfunc = endfunc;
-  process_active_write_data.aux = aux;
+  process_active_write_data.begin_func = begin_func;
+  process_active_write_data.proc_func = casefunc;
+  process_active_write_data.end_func = end_func;
+  process_active_write_data.func_aux = func_aux;
 
   not_canceled = 1;
 
   open_active_file ();
-  beginfunc (aux);
-  
-  /* There doesn't necessarily need to be an active file. */
+  begin_func (func_aux);
   if (vfm_source != NULL)
     vfm_source->class->read (vfm_source, process_active_file_write_case,
                              &process_active_write_data);
-  
-  endfunc (aux);
+  end_func (func_aux);
   close_active_file (&process_active_write_data);
 }
 
     {
       int code;
        
-      code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case);
+      code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case,
+                                     case_count + 1);
       switch (code)
        {
        case -1:
     lag_case ();
          
   /* Call the procedure if FILTER and PROCESS IF don't prohibit it. */
-  if (not_canceled && !exclude_this_case ())
-    not_canceled = data->procfunc (temp_case, data->aux);
+  if (not_canceled && !exclude_this_case (case_count + 1))
+    not_canceled = data->proc_func (temp_case, data->func_aux);
   
   case_count++;
   
 void
 process_active_file_output_case (void)
 {
-  vfm_sink_info.ncases++;
   vfm_sink->class->write (vfm_sink, temp_case);
 }
 \f
      So, in this case, we shouldn't have to replace the active
      file--it's just a waste of time and space. */
 
-  vfm_sink_info.ncases = 0;
-  vfm_sink_info.nval = dict_get_next_value_idx (default_dict);
-  vfm_sink_info.case_size = dict_get_case_size (default_dict);
-  
   if (vfm_sink == NULL)
     {
-      if (vfm_sink_info.case_size * vfm_source_info.ncases > set_max_workspace
-         && !workspace_overflow)
-       {
-         msg (MW, _("Workspace overflow predicted.  Max workspace is "
-                    "currently set to %d KB (%d cases at %d bytes each).  "
-                    "Writing active file to disk."),
-              set_max_workspace / 1024, set_max_workspace / vfm_sink_info.case_size,
-              vfm_sink_info.case_size);
-         
-         workspace_overflow = 1;
-       }
-
       if (workspace_overflow)
         vfm_sink = create_case_sink (&disk_sink_class, NULL);
       else
 static void
 make_temp_case (void)
 {
-  temp_case = xmalloc (vfm_sink_info.case_size);
+  temp_case = xmalloc (dict_get_case_size (default_dict));
 
   if (compaction_necessary)
     compaction_case = xmalloc (sizeof (struct ccase)
    Here is each nval count, with explanation, as set up by
    open_active_file():
 
-   vfm_source_info.nval: Number of `value's in the cases returned by
-   the source stream.  This value turns out not to be very useful, but
-   we maintain it anyway.
-
-   vfm_sink_info.nval: Number of `value's in the cases after all
-   transformations have been performed.  Never less than
-   vfm_source_info.nval.
-
    temp_dict->nval: Number of `value's in the cases after the
-   transformations leading up to TEMPORARY have been performed.  If
-   TEMPORARY was not specified, this is equal to vfm_sink_info.nval.
-   Never less than vfm_sink_info.nval.
+   transformations leading up to TEMPORARY have been performed.
 
    compaction_nval: Number of `value's in the cases after the
-   transformations leading up to TEMPORARY have been performed and the
-   case has been compacted by compact_case(), if compaction is
-   necessary.  This the number of `value's in the cases saved by the
-   sink stream.  (However, note that the cases passed to the sink
-   stream have not yet been compacted.  It is the responsibility of
-   the data sink to call compact_case().)  This may be less than,
-   greater than, or equal to vfm_source_info.nval.  `compaction'
-   becomes the new value of default_dict.nval after the procedure is
-   completed.
-
-   default_dict.nval: This is often an alias for temp_dict->nval.  As
-   such it can really have no separate existence until the procedure
-   is complete.  For this reason it should *not* be referenced inside
-   the execution of a procedure. */
+   transformations leading up to TEMPORARY have been performed
+   and the case has been compacted by compact_case(), if
+   compaction is necessary.  This the number of `value's in the
+   cases saved by the sink stream.  (However, note that the cases
+   passed to the sink stream have not yet been compacted.  It is
+   the responsibility of the data sink to call compact_case().)
+   `compaction' becomes the new value of default_dict.nval after
+   the procedure is completed.
+
+   default_dict.nval: This is often an alias for temp_dict->nval.
+   As such it can really have no separate existence until the
+   procedure is complete.  For this reason it should *not* be
+   referenced inside the execution of a procedure. */
 /* Makes all preparations for reading from the data source and writing
    to the data sink. */
 static void
   vector_initialization ();
   discard_ctl_stack ();
   setup_lag ();
-
-  /* Debug output. */
-  debug_printf (("vfm: reading from %s source, writing to %s sink.\n",
-                vfm_source->name, vfm_sink->name));
-  debug_printf (("vfm: vfm_source_info.nval=%d, vfm_sink_info.nval=%d, "
-                "temp_dict->nval=%d, compaction_nval=%d, "
-                "default_dict.nval=%d\n",
-                vfm_source_info.nval, vfm_sink_info.nval, temp_dict->nval,
-                compaction_nval, default_dict.nval));
 }
 \f
 /* Closes the active file. */
 close_active_file (struct write_case_data *data)
 {
   /* Close the current case group. */
-  if (case_count && data->endfunc != NULL)
-    data->endfunc (data->aux);
+  if (case_count && data->end_func != NULL)
+    data->end_func (data->func_aux);
 
   /* Stop lagging (catch up?). */
   if (n_lag)
     }
 
   vfm_source = vfm_sink->class->make_source (vfm_sink);
-  vfm_source_info.ncases = vfm_sink_info.ncases;
-  vfm_source_info.nval = compaction_nval;
-  vfm_source_info.case_size = (sizeof (struct ccase)
-                              + (compaction_nval - 1) * sizeof (union value));
 
   /* Old data sink is gone now. */
   free (vfm_sink);
 
   /* Clear VECTOR vectors. */
   dict_clear_vectors (default_dict);
-
-  debug_printf (("vfm: procedure complete\n\n"));
 }
 \f
 /* Disk case stream. */
 
+/* Information about disk sink or source. */
+struct disk_stream_info 
+  {
+    FILE *file;                 /* Output file. */
+    size_t case_cnt;            /* Number of cases written so far. */
+    size_t case_size;           /* Number of bytes in case. */
+  };
+
 /* Initializes the disk sink. */
 static void
 disk_sink_create (struct case_sink *sink)
 {
-  sink->aux = tmpfile ();
-  if (!sink->aux)
+  struct disk_stream_info *info = xmalloc (sizeof *info);
+  info->file = tmpfile ();
+  info->case_cnt = 0;
+  info->case_size = compaction_nval;
+  sink->aux = info;
+  if (info->file == NULL)
     {
       msg (ME, _("An error occurred attempting to create a temporary "
                 "file for use as the active file: %s."),
 static void
 disk_sink_write (struct case_sink *sink, struct ccase *c)
 {
-  FILE *file = sink->aux;
+  struct disk_stream_info *info = sink->aux;
   union value *src_case;
 
   if (compaction_necessary)
     }
   else src_case = c->data;
 
-  if (fwrite (src_case, sizeof *src_case * compaction_nval, 1, file) != 1)
+  info->case_cnt++;
+  if (fwrite (src_case, sizeof *src_case * compaction_nval, 1,
+              info->file) != 1)
     {
       msg (ME, _("An error occurred while attempting to write to a "
                 "temporary file used as the active file: %s."),
 static void
 disk_sink_destroy (struct case_sink *sink)
 {
-  FILE *file = sink->aux;
-  if (file != NULL)
-    fclose (file);
+  struct disk_stream_info *info = sink->aux;
+  if (info->file != NULL)
+    fclose (info->file);
 }
 
 /* Closes and destroys the sink and returns a disk source to read
 static struct case_source *
 disk_sink_make_source (struct case_sink *sink) 
 {
-  FILE *file = sink->aux;
-  
+  struct disk_stream_info *info = sink->aux;
+    
   /* Rewind the file. */
-  assert (file != NULL);
-  if (fseek (file, 0, SEEK_SET) != 0)
+  assert (info->file != NULL);
+  if (fseek (info->file, 0, SEEK_SET) != 0)
     {
       msg (ME, _("An error occurred while attempting to rewind a "
                 "temporary file used as the active file: %s."),
       err_failure ();
     }
   
-  return create_case_source (&disk_source_class, file);
+  return create_case_source (&disk_source_class, info);
 }
 
 /* Disk sink. */
 \f
 /* Disk source. */
 
+/* Returns the number of cases that will be read by
+   disk_source_read(). */
+static int
+disk_source_count (const struct case_source *source) 
+{
+  struct disk_stream_info *info = source->aux;
+
+  return info->case_cnt;
+}
+
 /* Reads all cases from the disk source and passes them one by one to
    write_case(). */
 static void
 disk_source_read (struct case_source *source,
                   write_case_func *write_case, write_case_data wc_data)
 {
-  FILE *file = source->aux;
+  struct disk_stream_info *info = source->aux;
   int i;
 
-  for (i = 0; i < vfm_source_info.ncases; i++)
+  for (i = 0; i < info->case_cnt; i++)
     {
-      if (!fread (temp_case, vfm_source_info.case_size, 1, file))
+      if (!fread (temp_case, info->case_size, 1, info->file))
        {
          msg (ME, _("An error occurred while attempting to read from "
               "a temporary file created for the active file: %s."),
 static void
 disk_source_destroy (struct case_source *source)
 {
-  FILE *file = source->aux;
-  if (file != NULL)
-    fclose (file);
+  struct disk_stream_info *info = source->aux;
+  if (info->file != NULL)
+    fclose (info->file);
+  free (info);
 }
 
 /* Disk source. */
 const struct case_source_class disk_source_class = 
   {
     "disk",
+    disk_source_count,
     disk_source_read,
     disk_source_destroy,
   };
 /* Memory sink data. */
 struct memory_sink_info
   {
+    size_t case_cnt;            /* Number of cases. */
+    size_t case_size;           /* Case size in bytes. */
     int max_cases;              /* Maximum cases before switching to disk. */
     struct case_list *head;     /* First case in list. */
     struct case_list *tail;     /* Last case in list. */
 /* Memory source data. */
 struct memory_source_info 
   {
+    size_t case_cnt;            /* Number of cases. */
+    size_t case_size;           /* Case size in bytes. */
     struct case_list *cases;    /* List of cases. */
   };
 
   sink->aux = info = xmalloc (sizeof *info);
 
   assert (compaction_nval > 0);
-  info->max_cases = set_max_workspace / (sizeof (union value) * compaction_nval);
+  info->case_cnt = 0;
+  info->case_size = compaction_nval * sizeof (union value);
+  info->max_cases = set_max_workspace / info->case_size;
   info->head = info->tail = NULL;
 }
 
   new_case = malloc (case_size);
 
   /* If we've got memory to spare then add it to the linked list. */
-  if (vfm_sink_info.ncases <= info->max_cases && new_case != NULL)
+  if (info->case_cnt <= info->max_cases && new_case != NULL)
     {
+      info->case_cnt++;
+
       /* Append case to linked list. */
       new_case->next = NULL;
       if (info->head != NULL)
   struct memory_source_info *source_info;
 
   source_info = xmalloc (sizeof *source_info);
+  source_info->case_cnt = sink_info->case_cnt;
+  source_info->case_size = sink_info->case_size;
   source_info->cases = sink_info->head;
 
   free (sink_info);
     memory_sink_make_source,
   };
 
+/* Returns the number of cases in the source. */
+static int
+memory_source_count (const struct case_source *source) 
+{
+  struct memory_source_info *info = source->aux;
+
+  return info->case_cnt;
+}
+
 /* Reads the case stream from memory and passes it to write_case(). */
 static void
 memory_source_read (struct case_source *source,
     {
       struct case_list *iter = info->cases;
       info->cases = iter->next;
-      memcpy (temp_case, &iter->c, vfm_source_info.case_size);
+      memcpy (temp_case, &iter->c, info->case_size);
       free (iter);
       
       if (!write_case (wc_data))
 const struct case_source_class memory_source_class = 
   {
     "memory",
+    memory_source_count,
     memory_source_read,
     memory_source_destroy,
   };
 \f
-#include "debug-print.h"
-
 /* Add temp_case to the lag queue. */
 static void
 lag_case (void)
 int
 procedure_write_case (write_case_data wc_data)
 {
+  struct procedure_aux_data *proc_aux = wc_data->aux;
+
   /* Index of current transformation. */
   int cur_trns;
 
   /* Return value: whether it's reasonable to write any more cases. */
   int more_cases = 1;
 
-  debug_printf ((_("transform: ")));
-
   cur_trns = f_trns;
   for (;;)
     {
       /* Output the case if this is temp_trns. */
       if (cur_trns == temp_trns)
        {
-         debug_printf (("REC"));
+          int case_limit;
 
          if (n_lag)
            lag_case ();
          
-         vfm_sink_info.ncases++;
          vfm_sink->class->write (vfm_sink, temp_case);
 
-         if (dict_get_case_limit (default_dict))
-           more_cases = (vfm_sink_info.ncases
-                          < dict_get_case_limit (default_dict));
+          proc_aux->cases_written++;
+          case_limit = dict_get_case_limit (default_dict);
+         if (case_limit != 0 && proc_aux->cases_written >= case_limit)
+            more_cases = 0;
        }
 
       /* Are we done? */
       if (cur_trns >= n_trns)
        break;
       
-      debug_printf (("$%d", cur_trns));
-
       /* Decide which transformation should come next. */
       {
        int code;
        
-       code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case);
+       code = t_trns[cur_trns]->proc (t_trns[cur_trns], temp_case,
+                                       proc_aux->cases_written + 1);
        switch (code)
          {
          case -1:
     }
 
   /* Call the beginning of group function. */
-  if (!case_count && wc_data->beginfunc != NULL)
-    wc_data->beginfunc (wc_data->aux);
+  if (!case_count && wc_data->begin_func != NULL)
+    wc_data->begin_func (wc_data->func_aux);
 
   /* Call the procedure if there is one and FILTER and PROCESS IF
      don't prohibit it. */
-  if (wc_data->procfunc != NULL && !exclude_this_case ())
-    wc_data->procfunc (temp_case, wc_data->aux);
+  if (wc_data->proc_func != NULL
+      && !exclude_this_case (proc_aux->cases_written + 1))
+    wc_data->proc_func (temp_case, wc_data->func_aux);
 
   case_count++;
   
 done:
-  debug_putc ('\n', stdout);
-
   clear_temp_case ();
   
   /* Return previously determined value. */
     }
 }
 
-/* Returns nonzero if this case should be exclude as specified on
-   FILTER or PROCESS IF, otherwise zero. */
+/* Returns nonzero if this case (numbered CASE_NUM) should be
+   exclude as specified on FILTER or PROCESS IF, otherwise
+   zero. */
 static int
-exclude_this_case (void)
+exclude_this_case (int case_num)
 {
   /* FILTER. */
   struct variable *filter_var = dict_get_filter (default_dict);
 
   /* PROCESS IF. */
   if (process_if_expr != NULL
-      && expr_evaluate (process_if_expr, temp_case, NULL) != 1.0)
+      && expr_evaluate (process_if_expr, temp_case, case_num, NULL) != 1.0)
     return 1;
 
   return 0;
   tab_submit (t);
 }
 
-/* This procfunc is substituted for the user-supplied procfunc when
+/* This proc_func is substituted for the user-supplied proc_func when
    SPLIT FILE is active.  This function forms a wrapper around that
-   procfunc by dividing the input into series. */
+   proc_func by dividing the input into series. */
 static int
-SPLIT_FILE_procfunc (struct ccase *c, void *data_)
+SPLIT_FILE_proc_func (struct ccase *c, void *data_)
 {
   struct write_case_data *data = data_;
-  static struct ccase *prev_case;
+  struct split_aux_data *split_aux = data->aux;
   struct variable *const *split;
   size_t split_cnt;
   size_t i;
      preserve the values of the case for later comparison. */
   if (case_count == 0)
     {
-      if (prev_case)
-       free (prev_case);
-      prev_case = xmalloc (vfm_sink_info.case_size);
-      memcpy (prev_case, c, vfm_sink_info.case_size);
+      memcpy (split_aux->prev_case, c, dict_get_case_size (default_dict));
 
       dump_splits (c);
-      if (data->beginfunc != NULL)
-       data->beginfunc (data->aux);
+      if (data->begin_func != NULL)
+       data->begin_func (data->func_aux);
       
-      return data->procfunc (c, data->aux);
+      return data->proc_func (c, data->func_aux);
     }
 
   /* Compare the value of each SPLIT FILE variable to the values on
       switch (v->type)
        {
        case NUMERIC:
-         if (c->data[v->fv].f != prev_case->data[v->fv].f)
+         if (c->data[v->fv].f != split_aux->prev_case->data[v->fv].f)
            goto not_equal;
          break;
        case ALPHA:
-         if (memcmp (c->data[v->fv].s, prev_case->data[v->fv].s, v->width))
+         if (memcmp (c->data[v->fv].s,
+                      split_aux->prev_case->data[v->fv].s, v->width))
            goto not_equal;
          break;
        default:
          assert (0);
        }
     }
-  return data->procfunc (c, data->aux);
+  return data->proc_func (c, data->func_aux);
   
 not_equal:
   /* The values of the SPLIT FILE variable are different from the
      values on the previous case.  That means that it's time to begin
      a new series. */
-  if (data->endfunc != NULL)
-    data->endfunc (data->aux);
+  if (data->end_func != NULL)
+    data->end_func (data->func_aux);
   dump_splits (c);
-  if (data->beginfunc != NULL)
-    data->beginfunc (data->aux);
-  memcpy (prev_case, c, vfm_sink_info.case_size);
-  return data->procfunc (c, data->aux);
+  if (data->begin_func != NULL)
+    data->begin_func (data->func_aux);
+  memcpy (split_aux->prev_case, c, dict_get_case_size (default_dict));
+  return data->proc_func (c, data->func_aux);
 }
 \f
 /* Case compaction. */
                       const struct case_source_class *class) 
 {
   return source != NULL && source->class == class;
-
 }
 
 struct case_sink *
 
   {
     const char *name;                   /* Identifying name. */
     
+    /* Returns the exact number of cases that READ will pass to
+       WRITE_CASE, if known, or -1 otherwise. */
+    int (*count) (const struct case_source *);
+
     /* Reads all the cases and calls WRITE_CASE passing the given
        AUX data for each one. */
     void (*read) (struct case_source *, write_case_func *, write_case_data);
 
 
 #include "var.h"
 
-/* Describes a data stream, either a source or a sink. */
-struct stream_info
-  {
-    int case_size;             /* Size of one case in bytes. */
-    int ncases;                        /* Number of cases. */
-    int nval;                  /* Number of `value' elements per case. */
-  };
-
-/* Information about the data source. */
-extern struct stream_info vfm_source_info;
-
-/* Information about the data sink. */
-extern struct stream_info vfm_sink_info;
-
 /* Nonzero if the case needs to have values deleted before being
    stored, zero otherwise. */
 extern int compaction_necessary;
 
-/* Number of values after compaction, or the same as
-   vfm_sink_info.nval, if compaction is not necessary. */
+/* Number of values after compaction. */
 extern int compaction_nval;
 
 /* Temporary case buffer with enough room for `compaction_nval'