Implemented long variable names a la spss V12.
[pspp-builds.git] / src / get.c
index eb1c1c040298d3027e3b06956acdeaf413fbf392..953ca7c5142f962c63dd4353f925ce085e8ed0cd 100644 (file)
--- a/src/get.c
+++ b/src/get.c
    02111-1307, USA. */
 
 #include <config.h>
-#include <assert.h>
+#include "error.h"
 #include <stdlib.h>
 #include "alloc.h"
-#include "avl.h"
+#include "case.h"
 #include "command.h"
+#include "dictionary.h"
 #include "error.h"
 #include "file-handle.h"
+#include "hash.h"
 #include "lexer.h"
 #include "misc.h"
-#include "pfm.h"
+#include "pfm-read.h"
+#include "pfm-write.h"
 #include "settings.h"
-#include "sfm.h"
+#include "sfm-read.h"
+#include "sfm-write.h"
 #include "str.h"
+#include "value-labels.h"
 #include "var.h"
 #include "vfm.h"
 #include "vfmP.h"
 
-#undef DEBUGGING
-/*#define DEBUGGING 1*/
 #include "debug-print.h"
 
-/* XSAVE transformation (and related SAVE, EXPORT procedures). */
-struct save_trns
+/* Rearranging and reducing a dictionary. */
+static void start_case_map (struct dictionary *);
+static struct case_map *finish_case_map (struct dictionary *);
+static void map_case (const struct case_map *,
+                      const struct ccase *, struct ccase *);
+static void destroy_case_map (struct case_map *);
+
+/* Operation type. */
+enum operation 
   {
-    struct trns_header h;
-    struct file_handle *f;     /* Associated system file. */
-    int nvar;                  /* Number of variables. */
-    int *var;                  /* Indices of variables. */
-    flt64 *case_buf;           /* Case transfer buffer. */
+    OP_READ,    /* GET or IMPORT. */
+    OP_SAVE,    /* SAVE or XSAVE. */
+    OP_EXPORT,  /* EXPORT. */
+    OP_MATCH    /* MATCH FILES. */
   };
 
-/* Options bits set by trim_dictionary(). */
-#define GTSV_OPT_COMPRESSED    001     /* Compression; (X)SAVE only. */
-#define GTSV_OPT_SAVE          002     /* The SAVE/XSAVE/EXPORT procedures. */
-#define GTSV_OPT_MATCH_FILES   004     /* The MATCH FILES procedure. */
-#define GTSV_OPT_NONE          0
-
-/* The file being read by the input program. */
-static struct file_handle *get_file;
-
-/* The transformation being used by the SAVE procedure. */
-static struct save_trns *trns;
-
-static int trim_dictionary (struct dictionary * dict, int *options);
-static int save_write_case_func (struct ccase *);
-static int save_trns_proc (struct trns_header *, struct ccase *);
-static void save_trns_free (struct trns_header *);
+static int trim_dictionary (struct dictionary *,
+                            enum operation, int *compress);
+\f
+/* GET input program. */
+struct get_pgm 
+  {
+    struct sfm_reader *reader;  /* System file reader. */
+    struct case_map *map;       /* Map from system file to active file dict. */
+    struct ccase bounce;        /* Bounce buffer. */
+  };
 
-#if DEBUGGING
-void dump_dict_variables (struct dictionary *);
-#endif
+static void get_pgm_free (struct get_pgm *);
 
 /* Parses the GET command. */
 int
 cmd_get (void)
 {
-  struct file_handle *handle;
-  struct dictionary *dict;
-  int options = GTSV_OPT_NONE;
+  struct get_pgm *pgm = NULL;
+  struct file_handle *fh;
+  struct dictionary *dict = NULL;
 
-  int i;
-  int nval;
+  pgm = xmalloc (sizeof *pgm);
+  pgm->reader = NULL;
+  pgm->map = NULL;
+  case_nullify (&pgm->bounce);
 
-  lex_match_id ("GET");
   discard_variables ();
 
   lex_match ('/');
   if (lex_match_id ("FILE"))
     lex_match ('=');
+  fh = fh_parse ();
+  if (fh == NULL)
+    goto error;
 
-  handle = fh_parse_file_handle ();
-  if (handle == NULL)
-    return CMD_FAILURE;
+  pgm->reader = sfm_open_reader (fh, &dict, NULL);
+  if (pgm->reader == NULL)
+    goto error;
+  case_create (&pgm->bounce, dict_get_next_value_idx (dict));
 
-  dict = sfm_read_dictionary (handle, NULL);
-  if (dict == NULL)
-    return CMD_FAILURE;
+  start_case_map (dict);
+  if (!trim_dictionary (dict, OP_READ, NULL))
+    goto error;
+  pgm->map = finish_case_map (dict);
 
-#if DEBUGGING
-  dump_dict_variables (dict);
-#endif
-  if (0 == trim_dictionary (dict, &options))
-    {
-      fh_close_handle (handle);
-      return CMD_FAILURE;
-    }
-#if DEBUGGING
-  dump_dict_variables (dict);
-#endif
-
-  /* Set the fv and lv elements of all variables remaining in the
-     dictionary. */
-  nval = 0;
-  for (i = 0; i < dict->nvar; i++)
-    {
-      struct variable *v = dict->var[i];
+  dict_destroy (default_dict);
+  default_dict = dict;
 
-      v->fv = nval;
-      nval += v->nv;
-    }
-  dict->nval = nval;
-  assert (nval);
+  vfm_source = create_case_source (&get_source_class, pgm);
 
-#if DEBUGGING
-  printf (_("GET translation table from file to memory:\n"));
-  for (i = 0; i < dict->nvar; i++)
-    {
-      struct variable *v = dict->var[i];
+  return CMD_SUCCESS;
+
+ error:
+  get_pgm_free (pgm);
+  if (dict != NULL)
+    dict_destroy (dict);
+  return CMD_FAILURE;
+}
 
-      printf (_("  %8s from %3d,%3d to %3d,%3d\n"), v->name,
-             v->get.fv, v->get.nv, v->fv, v->nv);
+/* Frees a struct get_pgm. */
+static void
+get_pgm_free (struct get_pgm *pgm) 
+{
+  if (pgm != NULL) 
+    {
+      sfm_close_reader (pgm->reader);
+      destroy_case_map (pgm->map);
+      case_destroy (&pgm->bounce);
+      free (pgm);
     }
-#endif
+}
 
-  restore_dictionary (dict);
+/* Clears internal state related to GET input procedure. */
+static void
+get_source_destroy (struct case_source *source)
+{
+  struct get_pgm *pgm = source->aux;
+  get_pgm_free (pgm);
+}
 
-  vfm_source = &get_source;
-  get_file = handle;
+/* Reads all the cases from the data file into C and passes them
+   to WRITE_CASE one by one, passing WC_DATA. */
+static void
+get_source_read (struct case_source *source,
+                 struct ccase *c,
+                 write_case_func *write_case, write_case_data wc_data)
+{
+  struct get_pgm *pgm = source->aux;
+  int ok;
 
-  return CMD_SUCCESS;
+  do
+    {
+      if (pgm->map == NULL)
+        ok = sfm_read_case (pgm->reader, c);
+      else
+        {
+          ok = sfm_read_case (pgm->reader, &pgm->bounce);
+          if (ok)
+            map_case (pgm->map, &pgm->bounce, c);
+        }
+
+      if (ok)
+        ok = write_case (wc_data);
+    }
+  while (ok);
 }
 
-/* Parses the SAVE (for X==0) and XSAVE (for X==1) commands.  */
-/* FIXME: save_dictionary() is too expensive.  It would make more
-   sense to copy just the first few fields of each variables (up to
-   `foo'): that's a SMOP. */
-int
-cmd_save_internal (int x)
-{
-  struct file_handle *handle;
-  struct dictionary *dict;
-  int options = GTSV_OPT_SAVE;
+const struct case_source_class get_source_class =
+  {
+    "GET",
+    NULL,
+    get_source_read,
+    get_source_destroy,
+  };
+\f
+/* XSAVE transformation and SAVE procedure. */
+struct save_trns
+  {
+    struct trns_header h;
+    struct sfm_writer *writer;  /* System file writer. */
+    struct case_map *map;       /* Map from active file to system file dict. */
+    struct ccase bounce;        /* Bounce buffer. */
+  };
 
-  struct save_trns *t;
-  struct sfm_write_info inf;
+static int save_write_case_func (struct ccase *, void *);
+static trns_proc_func save_trns_proc;
+static trns_free_func save_trns_free;
 
-  int i;
+/* Parses the SAVE or XSAVE command
+   and returns the parsed transformation. */
+static struct save_trns *
+cmd_save_internal (void)
+{
+  struct file_handle *fh = NULL;
+  struct dictionary *dict = NULL;
+  struct save_trns *t = NULL;
+  int compress = get_scompression ();
+  const int default_version = 3;
+  int version = default_version;
+  short no_name_table = 0;
+
+  t = xmalloc (sizeof *t);
+  t->h.proc = save_trns_proc;
+  t->h.free = save_trns_free;
+  t->writer = NULL;
+  t->map = NULL;
+  case_nullify (&t->bounce);
+  
 
-  lex_match_id ("SAVE");
+  /* Read most of the subcommands. */
+  for (;;)
+    {
+      if (lex_match_id ("VERSION"))
+       {
+         lex_match ('=');
+         if ( lex_force_num() ) 
+           {
+             lex_get();
+             version = tokval;
+             
+             if ( 0 == strncasecmp (tokid,"x", 1) ) 
+               {
+                 lex_get();
+                 no_name_table = 1;
+               }
 
-  lex_match ('/');
-  if (lex_match_id ("OUTFILE"))
-    lex_match ('=');
+           }
+       }
+      else if (lex_match_id ("OUTFILE"))
+       {
+         lex_match ('=');
+      
+         fh = fh_parse ();
+         if (fh == NULL)
+           goto error;
 
-  handle = fh_parse_file_handle ();
-  if (handle == NULL)
-    return CMD_FAILURE;
+       }
+      if ( ! lex_match('/')  ) 
+       break;
 
-  dict = save_dictionary ();
-#if DEBUGGING
-  dump_dict_variables (dict);
-#endif
-  for (i = 0; i < dict->nvar; i++)
-    dict->var[i]->foo = i;
-  if (0 == trim_dictionary (dict, &options))
-    {
-      fh_close_handle (handle);
-      return CMD_FAILURE;
     }
 
-#if DEBUGGING
-  dump_dict_variables (dict);
-#endif
+  if (token != '.')
+    {
+      lex_error (_("expecting end of command"));
+      goto error;
+    }
 
-  /* Write dictionary. */
-  inf.h = handle;
-  inf.dict = dict;
-  inf.compress = !!(options & GTSV_OPT_COMPRESSED);
-  if (!sfm_write_dictionary (&inf))
+  if ( fh == NULL ) 
     {
-      free_dictionary (dict);
-      fh_close_handle (handle);
-      return CMD_FAILURE;
+      msg ( ME, _("The required %s subcommand was not present"), "OUTFILE");
+      goto error;
     }
 
-  /* Fill in transformation structure. */
-  t = trns = xmalloc (sizeof *t);
-  t->h.proc = save_trns_proc;
-  t->h.free = save_trns_free;
-  t->f = handle;
-  t->nvar = dict->nvar;
-  t->var = xmalloc (sizeof *t->var * dict->nvar);
-  for (i = 0; i < dict->nvar; i++)
-    t->var[i] = dict->var[i]->foo;
-  t->case_buf = xmalloc (sizeof *t->case_buf * inf.case_size);
-  free_dictionary (dict);
-
-  if (x == 0)
-    /* SAVE. */
+  if ( version != default_version )
     {
-      procedure (NULL, save_write_case_func, NULL);
-      save_trns_free ((struct trns_header *) t);
+      msg (MW, _("Unsupported sysfile version: %d. Using version %d instead."),
+          version, default_version);
+
+      version = default_version;
     }
-  else
-    /* XSAVE. */
-    add_transformation ((struct trns_header *) t);
 
-  return CMD_SUCCESS;
+  dict = dict_clone (default_dict);
+  start_case_map (dict);
+  if (!trim_dictionary (dict, OP_SAVE, &compress))
+    goto error;
+  t->map = finish_case_map (dict);
+  if (t->map != NULL)
+    case_create (&t->bounce, dict_get_next_value_idx (dict));
+
+  t->writer = sfm_open_writer (fh, dict, compress, no_name_table);
+  if (t->writer == NULL)
+    goto error;
+
+  dict_destroy (dict);
+
+  return t;
+
+ error:
+  assert (t != NULL);
+  dict_destroy (dict);
+  save_trns_free (&t->h);
+  return NULL;
 }
 
 /* Parses and performs the SAVE procedure. */
 int
 cmd_save (void)
 {
-  return cmd_save_internal (0);
+  struct save_trns *t = cmd_save_internal ();
+  if (t != NULL) 
+    {
+      procedure (save_write_case_func, t);
+      save_trns_free (&t->h);
+      free(t);
+      return CMD_SUCCESS;
+    }
+  else
+    return CMD_FAILURE;
 }
 
 /* Parses the XSAVE transformation command. */
 int
 cmd_xsave (void)
 {
-  return cmd_save_internal (1);
+  struct save_trns *t = cmd_save_internal ();
+  if (t != NULL) 
+    {
+      add_transformation (&t->h);
+      return CMD_SUCCESS; 
+    }
+  else
+    return CMD_FAILURE;
 }
 
-static int
-save_write_case_func (struct ccase * c)
+/* Writes the given C to the file specified by T. */
+static void
+do_write_case (struct save_trns *t, struct ccase *c) 
 {
-  save_trns_proc ((struct trns_header *) trns, c);
-  return 1;
+  if (t->map == NULL)
+    sfm_write_case (t->writer, c);
+  else 
+    {
+      map_case (t->map, c, &t->bounce);
+      sfm_write_case (t->writer, &t->bounce);
+    }
 }
 
+/* Writes case C to the system file specified on SAVE. */
 static int
-save_trns_proc (struct trns_header * t unused, struct ccase * c)
+save_write_case_func (struct ccase *c, void *aux UNUSED)
 {
-  flt64 *p = trns->case_buf;
-  int i;
-
-  for (i = 0; i < trns->nvar; i++)
-    {
-      struct variable *v = default_dict.var[trns->var[i]];
-      if (v->type == NUMERIC)
-       {
-         double src = c->data[v->fv].f;
-         if (src == SYSMIS)
-           *p++ = -FLT64_MAX;
-         else
-           *p++ = src;
-       }
-      else
-       {
-         memcpy (p, c->data[v->fv].s, v->width);
-         memset (&((char *) p)[v->width], ' ',
-                 REM_RND_UP (v->width, sizeof *p));
-         p += DIV_RND_UP (v->width, sizeof *p);
-       }
-    }
-
-  sfm_write_case (trns->f, trns->case_buf, p - trns->case_buf);
-  return -1;
+  do_write_case (aux, c);
+  return 1;
 }
 
-static void
-save_trns_free (struct trns_header *pt)
+/* Writes case C to the system file specified on XSAVE. */
+static int
+save_trns_proc (struct trns_header *h, struct ccase *c, int case_num UNUSED)
 {
-  struct save_trns *t = (struct save_trns *) pt;
-
-  fh_close_handle (t->f);
-  free (t->var);
-  free (t->case_buf);
-  free (t);
+  struct save_trns *t = (struct save_trns *) h;
+  do_write_case (t, c);
+  return -1;
 }
 
-/* Deletes NV variables from DICT, starting at index FIRST.  The
-   variables must have consecutive indices.  The variables are cleared
-   and freed. */
+/* Frees a SAVE transformation. */
 static void
-dict_delete_run (struct dictionary *dict, int first, int nv)
+save_trns_free (struct trns_header *t_)
 {
-  int i;
+  struct save_trns *t = (struct save_trns *) t_;
 
-  for (i = first; i < first + nv; i++)
-    {
-      clear_variable (dict, dict->var[i]);
-      free (dict->var[i]);
-    }
-  for (i = first; i < dict->nvar - nv; i++)
+  if (t != NULL) 
     {
-      dict->var[i] = dict->var[i + nv];
-      dict->var[i]->index -= nv;
+      sfm_close_writer (t->writer);
+      destroy_case_map (t->map);
+      case_destroy (&t->bounce);
     }
-  dict->nvar -= nv;
 }
 
-static int rename_variables (struct dictionary * dict);
+static int rename_variables (struct dictionary *dict);
+
+/* Commands that read and write system files share a great deal
+   of common syntactic structure for rearranging and dropping
+   variables.  This function parses this syntax and modifies DICT
+   appropriately.
 
-/* The GET and SAVE commands have a common structure after the
-   FILE/OUTFILE subcommand.  This function parses this structure and
-   returns nonzero on success, zero on failure.  It both reads
-   *OPTIONS, for the GTSV_OPT_SAVE bit, and writes it, for the
-   GTSV_OPT_COMPRESSED bit. */
+   OP is the operation being performed.  For operations that
+   write a system file, *COMPRESS is set to 1 if the system file
+   should be compressed, 0 otherwise.
+   
+   Returns nonzero on success, zero on failure. */
 /* FIXME: IN, FIRST, LAST, MAP. */
 static int
-trim_dictionary (struct dictionary *dict, int *options)
+trim_dictionary (struct dictionary *dict, enum operation op, int *compress)
 {
-  if (set_scompression)
-    *options |= GTSV_OPT_COMPRESSED;
+  assert ((compress != NULL) == (op == OP_SAVE));
+  if (get_scompression())
+    *compress = 1;
 
-  if (*options & GTSV_OPT_SAVE)
+  if (op == OP_SAVE || op == OP_EXPORT)
     {
-      int i;
-
       /* Delete all the scratch variables. */
-      for (i = 0; i < dict->nvar; i++)
-       {
-         int j;
-         
-         if (dict->var[i]->name[0] != '#')
-           continue;
-
-         /* Find a run of variables to be deleted. */
-         for (j = i + 1; j < dict->nvar; j++)
-           if (dict->var[j]->name[0] != '#')
-             break;
-
-         /* Actually delete 'em. */
-         dict_delete_run (dict, i, j - i);
-       }
+      struct variable **v;
+      size_t nv;
+      size_t i;
+
+      v = xmalloc (sizeof *v * dict_get_var_cnt (dict));
+      nv = 0;
+      for (i = 0; i < dict_get_var_cnt (dict); i++) 
+        if (dict_class_from_id (dict_get_var (dict, i)->name) == DC_SCRATCH)
+          v[nv++] = dict_get_var (dict, i);
+      dict_delete_vars (dict, v, nv);
+      free (v);
     }
   
-  while ((*options & GTSV_OPT_MATCH_FILES) || lex_match ('/'))
+  while (op == OP_MATCH || lex_match ('/'))
     {
-      if (!(*options & GTSV_OPT_MATCH_FILES) && lex_match_id ("COMPRESSED"))
-       *options |= GTSV_OPT_COMPRESSED;
-      else if (!(*options & GTSV_OPT_MATCH_FILES) && lex_match_id ("UNCOMPRESSED"))
-       *options &= ~GTSV_OPT_COMPRESSED;
+      if (op == OP_SAVE && lex_match_id ("COMPRESSED"))
+       *compress = 1;
+      else if (op == OP_SAVE && lex_match_id ("UNCOMPRESSED"))
+       *compress = 0;
       else if (lex_match_id ("DROP"))
        {
          struct variable **v;
          int nv;
-         int i;
 
          lex_match ('=');
          if (!parse_variables (dict, &v, &nv, PV_NONE))
            return 0;
-
-         /* Loop through the variables to delete. */
-         for (i = 0; i < nv;)
-           {
-             int j;
-
-             /* Find a run of variables to be deleted. */
-             for (j = i + 1; j < nv; j++)
-               if (v[j]->index != v[j - 1]->index + 1)
-                 break;
-
-             /* Actually delete 'em. */
-             dict_delete_run (dict, v[i]->index, j - i);
-             i = j;
-           }
+          dict_delete_vars (dict, v, nv);
+          free (v);
        }
       else if (lex_match_id ("KEEP"))
        {
          struct variable **v;
          int nv;
+          int i;
 
          lex_match ('=');
          if (!parse_variables (dict, &v, &nv, PV_NONE))
            return 0;
 
-         /* Reorder the dictionary so that the kept variables are at
-            the beginning. */
-         {
-           int i1;
-           
-           for (i1 = 0; i1 < nv; i1++)
-             {
-               int i2 = v[i1]->index;
-
-               /* Swap variables with indices i1 and i2. */
-               struct variable *t = dict->var[i1];
-               dict->var[i1] = dict->var[i2];
-               dict->var[i2] = t;
-               dict->var[i1]->index = i1;
-               dict->var[i2]->index = i2;
-             }
-
-           free (v);
-         }
-         
-         /* Delete all but the first NV variables from the
-            dictionary. */
-         {
-           int i;
-           for (i = nv; i < dict->nvar; i++)
-             {
-               clear_variable (dict, dict->var[i]);
-               free (dict->var[i]);
-             }
-         }
-         dict->var = xrealloc (dict->var, sizeof *dict->var * nv);
-         dict->nvar = nv;
+          /* Move the specified variables to the beginning. */
+          dict_reorder_vars (dict, v, nv);
+          
+          /* Delete the remaining variables. */
+          v = xrealloc (v, (dict_get_var_cnt (dict) - nv) * sizeof *v);
+          for (i = nv; i < dict_get_var_cnt (dict); i++)
+            v[i - nv] = dict_get_var (dict, i);
+          dict_delete_vars (dict, v, dict_get_var_cnt (dict) - nv);
+          free (v);
        }
       else if (lex_match_id ("RENAME"))
        {
@@ -421,14 +441,14 @@ trim_dictionary (struct dictionary *dict, int *options)
          return 0;
        }
 
-      if (dict->nvar == 0)
+      if (dict_get_var_cnt (dict) == 0)
        {
          msg (SE, _("All variables deleted from system file dictionary."));
          return 0;
        }
 
-      if (*options & GTSV_OPT_MATCH_FILES)
-       return 1;
+      if (op == OP_MATCH)
+        goto success;
     }
 
   if (token != '.')
@@ -436,13 +456,16 @@ trim_dictionary (struct dictionary *dict, int *options)
       lex_error (_("expecting end of command"));
       return 0;
     }
-  
+
+ success:
+  if (op != OP_MATCH)
+    dict_compact_values (dict);
   return 1;
 }
 
 /* Parses and performs the RENAME subcommand of GET and SAVE. */
 static int
-rename_variables (struct dictionary * dict)
+rename_variables (struct dictionary *dict)
 {
   int i;
 
@@ -451,6 +474,7 @@ rename_variables (struct dictionary * dict)
   struct variable **v;
   char **new_names;
   int nv, nn;
+  char *err_name;
 
   int group;
 
@@ -465,9 +489,9 @@ rename_variables (struct dictionary * dict)
       if (!lex_force_match ('=')
          || !lex_force_id ())
        return 0;
-      if (!strncmp (tokid, v->name, 8))
+      if (!strncmp (tokid, v->name, SHORT_NAME_LEN))
        return 1;
-      if (is_dict_varname (dict, tokid))
+      if (dict_lookup_var (dict, tokid) != NULL)
        {
          msg (SE, _("Cannot rename %s as %s because there already exists "
                     "a variable named %s.  To rename variables with "
@@ -477,7 +501,7 @@ rename_variables (struct dictionary * dict)
          return 0;
        }
       
-      rename_variable (dict, v, tokid);
+      dict_rename_var (dict, v, tokid);
       lex_get ();
       return 1;
     }
@@ -491,43 +515,35 @@ rename_variables (struct dictionary * dict)
       int old_nv = nv;
 
       if (!parse_variables (dict, &v, &nv, PV_NO_DUPLICATE | PV_APPEND))
-       goto lossage;
+       goto done;
       if (!lex_match ('='))
        {
          msg (SE, _("`=' expected after variable list."));
-         goto lossage;
+         goto done;
        }
       if (!parse_DATA_LIST_vars (&new_names, &nn, PV_APPEND | PV_NO_SCRATCH))
-       goto lossage;
+       goto done;
       if (nn != nv)
        {
-         msg (SE, _("Number of variables on left side of `=' (%d) do not "
+         msg (SE, _("Number of variables on left side of `=' (%d) does not "
               "match number of variables on right side (%d), in "
               "parenthesized group %d of RENAME subcommand."),
               nv - old_nv, nn - old_nv, group);
-         goto lossage;
+         goto done;
        }
       if (!lex_force_match (')'))
-       goto lossage;
+       goto done;
       group++;
     }
 
-  for (i = 0; i < nv; i++)
-    avl_force_delete (dict->var_by_name, v[i]);
-  for (i = 0; i < nv; i++)
+  if (!dict_rename_vars (dict, v, new_names, nv, &err_name)) 
     {
-      strcpy (v[i]->name, new_names[i]);
-      if (NULL != avl_insert (dict->var_by_name, v[i]))
-       {
-         msg (SE, _("Duplicate variables name %s."), v[i]->name);
-         goto lossage;
-       }
+      msg (SE, _("Requested renaming duplicates variable name %s."), err_name);
+      goto done;
     }
   success = 1;
 
-lossage:
-  /* The label is a bit of a misnomer, we actually come here on any
-     sort of return. */
+done:
   for (i = 0; i < nn; i++)
     free (new_names[i]);
   free (new_names);
@@ -535,56 +551,94 @@ lossage:
 
   return success;
 }
+\f
+/* EXPORT procedure. */
+struct export_proc 
+  {
+    struct pfm_writer *writer;  /* System file writer. */
+    struct case_map *map;       /* Map from active file to system file dict. */
+    struct ccase bounce;        /* Bounce buffer. */
+  };
 
-#if DEBUGGING
-void
-dump_dict_variables (struct dictionary * dict)
+static int export_write_case_func (struct ccase *, void *);
+static void export_proc_free (struct export_proc *);
+     
+/* Parses the EXPORT command.  */
+/* FIXME: same as cmd_save_internal(). */
+int
+cmd_export (void)
 {
-  int i;
+  struct file_handle *fh;
+  struct dictionary *dict;
+  struct export_proc *proc;
+
+  proc = xmalloc (sizeof *proc);
+  proc->writer = NULL;
+  proc->map = NULL;
+  case_nullify (&proc->bounce);
+
+  lex_match ('/');
+  if (lex_match_id ("OUTFILE"))
+    lex_match ('=');
+  fh = fh_parse ();
+  if (fh == NULL)
+    return CMD_FAILURE;
+
+  dict = dict_clone (default_dict);
+  start_case_map (dict);
+  if (!trim_dictionary (dict, OP_EXPORT, NULL))
+    goto error;
+  proc->map = finish_case_map (dict);
+  if (proc->map != NULL)
+    case_create (&proc->bounce, dict_get_next_value_idx (dict));
+
+  proc->writer = pfm_open_writer (fh, dict);
+  if (proc->writer == NULL)
+    goto error;
+  
+  dict_destroy (dict);
+
+  procedure (export_write_case_func, proc);
+  export_proc_free (proc);
+  free (proc);
+
+  return CMD_SUCCESS;
 
-  printf (_("\nVariables in dictionary:\n"));
-  for (i = 0; i < dict->nvar; i++)
-    printf ("%s, ", dict->var[i]->name);
-  printf ("\n");
+ error:
+  dict_destroy (dict);
+  export_proc_free (proc);
+  free (proc);
+  return CMD_FAILURE;
 }
-#endif
-\f
-/* Clears internal state related to GET input procedure. */
-static void
-get_source_destroy_source (void)
+
+/* Writes case C to the EXPORT file. */
+static int
+export_write_case_func (struct ccase *c, void *aux) 
 {
-  /* It is not necessary to destroy the dictionary because if we get
-     to this point then the dictionary is default_dict. */
-  fh_close_handle (get_file);
+  struct export_proc *proc = aux;
+  if (proc->map == NULL)
+    pfm_write_case (proc->writer, c);
+  else 
+    {
+      map_case (proc->map, c, &proc->bounce);
+      pfm_write_case (proc->writer, &proc->bounce);
+    }
+  return 1;
 }
 
-/* Reads all the cases from the data file and passes them to
-   write_case(). */
 static void
-get_source_read (void)
+export_proc_free (struct export_proc *proc) 
 {
-  while (sfm_read_case (get_file, temp_case->data, &default_dict)
-        && write_case ())
-    ;
-  get_source_destroy_source ();
+  if (proc != NULL) 
+    {
+      pfm_close_writer (proc->writer);
+      destroy_case_map (proc->map);
+      case_destroy (&proc->bounce);
+    }
 }
-
-struct case_stream get_source =
-  {
-    NULL,
-    get_source_read,
-    NULL,
-    NULL,
-    get_source_destroy_source,
-    NULL,
-    "GET",
-  };
-
 \f
 /* MATCH FILES. */
 
-#undef DEBUGGING
-/*#define DEBUGGING 1*/
 #include "debug-print.h"
 
 /* File types. */
@@ -603,50 +657,65 @@ struct mtf_file
     
     int type;                  /* One of MTF_*. */
     struct variable **by;      /* List of BY variables for this file. */
-    struct file_handle *handle;        /* File handle for the file. */
+    struct file_handle *handle; /* File handle. */
+    struct sfm_reader *reader;  /* System file reader. */
     struct dictionary *dict;   /* Dictionary from system file. */
-    char in[9];                        /* Name of the variable from IN=. */
-    char first[9], last[9];    /* Name of the variables from FIRST=, LAST=. */
-    union value *input;                /* Input record. */
+    char in[SHORT_NAME_LEN + 1];    /* Name of the variable from IN=. */
+    char first[SHORT_NAME_LEN + 1];
+    char last[SHORT_NAME_LEN + 1];     /* Name of the variables from FIRST=, LAST=. */
+    struct ccase input;         /* Input record. */
   };
 
-/* All the files mentioned on FILE= or TABLE=. */
-static struct mtf_file *mtf_head, *mtf_tail;
+/* MATCH FILES procedure. */
+struct mtf_proc 
+  {
+    struct mtf_file *head;      /* First file mentioned on FILE or TABLE. */
+    struct mtf_file *tail;      /* Last file mentioned on FILE or TABLE. */
+    
+    struct variable **by;       /* Variables on the BY subcommand. */
+    size_t by_cnt;              /* Number of variables on BY subcommand. */
 
-/* Variables on the BY subcommand. */
-static struct variable **mtf_by;
-static int mtf_n_by;
+    struct dictionary *dict;    /* Dictionary of output file. */
+    struct case_sink *sink;     /* Sink to receive output. */
+    struct ccase *mtf_case;     /* Case used for output. */
 
-/* Master dictionary. */
-static struct dictionary *mtf_master;
+    unsigned seq_num;           /* Have we initialized this variable? */
+    unsigned *seq_nums;         /* Sequence numbers for each var in dict. */
+  };
 
-static void mtf_free (void);
-static void mtf_free_file (struct mtf_file *file);
-static int mtf_merge_dictionary (struct mtf_file *f);
-static void mtf_delete_file_in_place (struct mtf_file **file);
+static void mtf_free (struct mtf_proc *);
+static void mtf_free_file (struct mtf_file *);
+static int mtf_merge_dictionary (struct dictionary *const, struct mtf_file *);
+static void mtf_delete_file_in_place (struct mtf_proc *, struct mtf_file **);
 
-static void mtf_read_nonactive_records (void);
-static void mtf_processing_finish (void);
-static int mtf_processing (struct ccase *);
+static void mtf_read_nonactive_records (void *);
+static void mtf_processing_finish (void *);
+static int mtf_processing (struct ccase *, void *);
 
 static char *var_type_description (struct variable *);
 
+static void set_master (struct variable *, struct variable *master);
+static struct variable *get_master (struct variable *);
+
 /* Parse and execute the MATCH FILES command. */
 int
 cmd_match_files (void)
 {
+  struct mtf_proc mtf;
   struct mtf_file *first_table = NULL;
+  struct mtf_file *iter;
   
   int seen = 0;
   
-  lex_match_id ("MATCH");
-  lex_match_id ("FILES");
-
-  mtf_head = mtf_tail = NULL;
-  mtf_by = NULL;
-  mtf_n_by = 0;
-  mtf_master = new_dictionary (0);
-  mtf_master->N = default_dict.N;
+  mtf.head = mtf.tail = NULL;
+  mtf.by = NULL;
+  mtf.by_cnt = 0;
+  mtf.dict = dict_create ();
+  mtf.sink = NULL;
+  mtf.mtf_case = NULL;
+  mtf.seq_num = 0;
+  mtf.seq_nums = NULL;
+  dict_set_case_limit (mtf.dict, dict_get_case_limit (default_dict));
   
   do
     {
@@ -657,29 +726,24 @@ cmd_match_files (void)
          if (seen & 1)
            {
              msg (SE, _("The BY subcommand may be given once at most."));
-             goto lossage;
+             goto error;
            }
          seen |= 1;
              
          lex_match ('=');
-         if (!parse_variables (mtf_master, &mtf_by, &mtf_n_by,
+         if (!parse_variables (mtf.dict, &mtf.by, &mtf.by_cnt,
                                PV_NO_DUPLICATE | PV_NO_SCRATCH))
-           goto lossage;
+           goto error;
        }
       else if (token != T_ID)
        {
          lex_error (NULL);
-         goto lossage;
+         goto error;
        }
       else if (lex_id_match ("FILE", tokid) || lex_id_match ("TABLE", tokid))
        {
          struct mtf_file *file = xmalloc (sizeof *file);
 
-         file->in[0] = file->first[0] = file->last[0] = '\0';
-         file->dict = NULL;
-         file->by = NULL;
-         file->input = NULL;
-
          if (lex_match_id ("FILE"))
            file->type = MTF_FILE;
          else if (lex_match_id ("TABLE"))
@@ -690,16 +754,25 @@ cmd_match_files (void)
          else
            assert (0);
 
+         file->by = NULL;
+          file->handle = NULL;
+          file->reader = NULL;
+         file->dict = NULL;
+         file->in[0] = '\0';
+          file->first[0] = '\0';
+          file->last[0] = '\0';
+          case_nullify (&file->input);
+
          /* FILEs go first, then TABLEs. */
          if (file->type == MTF_TABLE || first_table == NULL)
            {
              file->next = NULL;
-             file->prev = mtf_tail;
-             if (mtf_tail)
-               mtf_tail->next = file;
-             mtf_tail = file;
-             if (mtf_head == NULL)
-               mtf_head = file;
+             file->prev = mtf.tail;
+             if (mtf.tail)
+               mtf.tail->next = file;
+             mtf.tail = file;
+             if (mtf.head == NULL)
+               mtf.head = file;
              if (file->type == MTF_TABLE && first_table == NULL)
                first_table = file;
            }
@@ -711,7 +784,7 @@ cmd_match_files (void)
              if (first_table->prev)
                first_table->prev->next = file;
              else
-               mtf_head = file;
+               mtf.head = file;
              first_table->prev = file;
            }
          
@@ -719,13 +792,14 @@ cmd_match_files (void)
          
          if (lex_match ('*'))
            {
-             file->handle = NULL;
-
+              file->handle = NULL;
+             file->reader = NULL;
+              
              if (seen & 2)
                {
                  msg (SE, _("The active file may not be specified more "
                             "than once."));
-                 goto lossage;
+                 goto error;
                }
              seen |= 2;
 
@@ -734,26 +808,32 @@ cmd_match_files (void)
                {
                  msg (SE, _("Cannot specify the active file since no active "
                             "file has been defined."));
-                 goto lossage;
+                 goto error;
                }
+
+              if (temporary != 0)
+                {
+                  msg (SE,
+                       _("MATCH FILES may not be used after TEMPORARY when "
+                         "the active file is an input source.  "
+                         "Temporary transformations will be made permanent."));
+                  cancel_temporary (); 
+                }
+
+              file->dict = default_dict;
            }
          else
            {
-             file->handle = fh_parse_file_handle ();
-             if (!file->handle)
-               goto lossage;
-           }
+              file->handle = fh_parse ();
+             if (file->handle == NULL)
+               goto error;
 
-         if (file->handle)
-           {
-             file->dict = sfm_read_dictionary (file->handle, NULL);
-             if (!file->dict)
-               goto lossage;
+              file->reader = sfm_open_reader (file->handle, &file->dict, NULL);
+              if (file->reader == NULL)
+                goto error;
+
+              case_create (&file->input, dict_get_next_value_idx (file->dict));
            }
-         else
-           file->dict = &default_dict;
-         if (!mtf_merge_dictionary (file))
-           goto lossage;
        }
       else if (lex_id_match ("IN", tokid)
               || lex_id_match ("FIRST", tokid)
@@ -762,36 +842,39 @@ cmd_match_files (void)
          const char *sbc;
          char *name;
          
-         if (mtf_tail == NULL)
+         if (mtf.tail == NULL)
            {
              msg (SE, _("IN, FIRST, and LAST subcommands may not occur "
                         "before the first FILE or TABLE."));
-             goto lossage;
+             goto error;
            }
 
          if (lex_match_id ("IN"))
            {
-             name = mtf_tail->in;
+             name = mtf.tail->in;
              sbc = "IN";
            }
          else if (lex_match_id ("FIRST"))
            {
-             name = mtf_tail->first;
+             name = mtf.tail->first;
              sbc = "FIRST";
            }
          else if (lex_match_id ("LAST"))
            {
-             name = mtf_tail->last;
+             name = mtf.tail->last;
              sbc = "LAST";
            }
-         else
-           assert (0);
+         else 
+            {
+              assert (0);
+              abort ();
+            }
 
          lex_match ('=');
          if (token != T_ID)
            {
              lex_error (NULL);
-             goto lossage;
+             goto error;
            }
 
          if (*name)
@@ -799,34 +882,32 @@ cmd_match_files (void)
              msg (SE, _("Multiple %s subcommands for a single FILE or "
                         "TABLE."),
                   sbc);
-             goto lossage;
+             goto error;
            }
          strcpy (name, tokid);
          lex_get ();
 
-         if (!create_variable (mtf_master, name, NUMERIC, 0))
+         if (!dict_create_var (mtf.dict, name, 0))
            {
              msg (SE, _("Duplicate variable name %s while creating %s "
                         "variable."),
                   name, sbc);
-             goto lossage;
+             goto error;
            }
        }
       else if (lex_id_match ("RENAME", tokid)
               || lex_id_match ("KEEP", tokid)
               || lex_id_match ("DROP", tokid))
        {
-         int options = GTSV_OPT_MATCH_FILES;
-         
-         if (mtf_tail == NULL)
+         if (mtf.tail == NULL)
            {
              msg (SE, _("RENAME, KEEP, and DROP subcommands may not occur "
                         "before the first FILE or TABLE."));
-             goto lossage;
+             goto error;
            }
 
-         if (!trim_dictionary (mtf_tail->dict, &options))
-           goto lossage;
+         if (!trim_dictionary (mtf.tail->dict, OP_MATCH, NULL))
+           goto error;
        }
       else if (lex_match_id ("MAP"))
        {
@@ -835,54 +916,46 @@ cmd_match_files (void)
       else
        {
          lex_error (NULL);
-         goto lossage;
+         goto error;
        }
     }
   while (token != '.');
 
+  for (iter = mtf.head; iter != NULL; iter = iter->next) 
+    mtf_merge_dictionary (mtf.dict, iter);
+
   if (seen & 4)
     {
       if (!(seen & 1))
        {
          msg (SE, _("The BY subcommand is required when a TABLE subcommand "
                     "is given."));
-         goto lossage;
+         goto error;
        }
     }
 
   if (seen & 1)
     {
-      struct mtf_file *iter;
-
-      for (iter = mtf_head; iter; iter = iter->next)
+      for (iter = mtf.head; iter != NULL; iter = iter->next)
        {
          int i;
          
-         iter->by = xmalloc (sizeof *iter->by * mtf_n_by);
+         iter->by = xmalloc (sizeof *iter->by * mtf.by_cnt);
 
-         for (i = 0; i < mtf_n_by; i++)
+         for (i = 0; i < mtf.by_cnt; i++)
            {
-             iter->by[i] = find_dict_variable (iter->dict, mtf_by[i]->name);
+             iter->by[i] = dict_lookup_var (iter->dict, mtf.by[i]->name);
              if (iter->by[i] == NULL)
                {
                  msg (SE, _("File %s lacks BY variable %s."),
-                      iter->handle ? fh_handle_name (iter->handle) : "*",
-                      mtf_by[i]->name);
-                 goto lossage;
+                      iter->handle ? handle_get_name (iter->handle) : "*",
+                      mtf.by[i]->name);
+                 goto error;
                }
            }
        }
     }
 
-#if DEBUGGING
-  {
-    /* From sfm-read.c. */
-    extern void dump_dictionary (struct dictionary *);
-
-    dump_dictionary (mtf_master);
-  }
-#endif
-
   /* MATCH FILES performs an n-way merge on all its input files.
      Abstract algorithm:
 
@@ -914,48 +987,57 @@ cmd_match_files (void)
      because there's no function to read a record from the active
      file; instead, it has to be done using callbacks.
 
-     FIXME: A better algorithm would use a heap for finding minimum
-     values, or replacement selection, as described by Knuth in _Art
-     of Computer Programming, Vol. 3_.  The SORT CASES procedure does
-     this, and perhaps some of its code could be adapted. */
+     FIXME: For merging large numbers of files (more than 10?) a
+     better algorithm would use a heap for finding minimum
+     values. */
 
   if (!(seen & 2))
     discard_variables ();
 
-  temporary = 2;
-  temp_dict = mtf_master;
-  temp_trns = n_trns;
+  mtf.sink = create_case_sink (&storage_sink_class, mtf.dict, NULL);
+
+  mtf.seq_nums = xmalloc (dict_get_var_cnt (mtf.dict)
+                          * sizeof *mtf.seq_nums);
+  memset (mtf.seq_nums, 0,
+          dict_get_var_cnt (mtf.dict) * sizeof *mtf.seq_nums);
+  mtf.mtf_case = xmalloc (dict_get_case_size (mtf.dict));
 
-  process_active_file (mtf_read_nonactive_records, mtf_processing,
-                      mtf_processing_finish);
-  mtf_master = NULL;
+  mtf_read_nonactive_records (NULL);
+  if (seen & 2)
+    procedure (mtf_processing, NULL);
+  mtf_processing_finish (NULL);
+
+  dict_destroy (default_dict);
+  default_dict = mtf.dict;
+  mtf.dict = NULL;
+  vfm_source = mtf.sink->class->make_source (mtf.sink);
+  free_case_sink (mtf.sink);
   
-  mtf_free ();
+  mtf_free (&mtf);
   return CMD_SUCCESS;
   
-lossage:
-  mtf_free ();
+error:
+  mtf_free (&mtf);
   return CMD_FAILURE;
 }
 
 /* Repeats 2...8 an arbitrary number of times. */
 static void
-mtf_processing_finish (void)
+mtf_processing_finish (void *mtf_)
 {
+  struct mtf_proc *mtf = mtf_;
+  struct mtf_file *iter;
+
   /* Find the active file and delete it. */
-  {
-    struct mtf_file *iter;
-    
-    for (iter = mtf_head; iter; iter = iter->next)
-      if (iter->handle == NULL)
-       {
-         mtf_delete_file_in_place (&iter);
-         break;
-       }
-  }
+  for (iter = mtf->head; iter; iter = iter->next)
+    if (iter->handle == NULL)
+      {
+        mtf_delete_file_in_place (mtf, &iter);
+        break;
+      }
   
-  while (mtf_head && mtf_head->type == MTF_FILE)
-    if (!mtf_processing (temp_case))
+  while (mtf->head && mtf->head->type == MTF_FILE)
+    if (!mtf_processing (NULL, mtf))
       break;
 }
 
@@ -985,37 +1067,37 @@ var_type_description (struct variable *v)
 static void
 mtf_free_file (struct mtf_file *file)
 {
-  fh_close_handle (file->handle);
-  if (file->dict && file->dict != &default_dict)
-    free_dictionary (file->dict);
   free (file->by);
-  if (file->handle)
-    free (file->input);
+  sfm_close_reader (file->reader);
+  if (file->dict != default_dict)
+    dict_destroy (file->dict);
+  case_destroy (&file->input);
   free (file);
 }
 
 /* Free all the data for the MATCH FILES procedure. */
 static void
-mtf_free (void)
+mtf_free (struct mtf_proc *mtf)
 {
   struct mtf_file *iter, *next;
 
-  for (iter = mtf_head; iter; iter = next)
+  for (iter = mtf->head; iter; iter = next)
     {
       next = iter->next;
 
       mtf_free_file (iter);
     }
   
-  free (mtf_by);
-  if (mtf_master)
-    free_dictionary (mtf_master);
+  free (mtf->by);
+  if (mtf->dict)
+    dict_destroy (mtf->dict);
+  free (mtf->seq_nums);
 }
 
 /* Remove *FILE from the mtf_file chain.  Make *FILE point to the next
    file in the chain, or to NULL if was the last in the chain. */
 static void
-mtf_delete_file_in_place (struct mtf_file **file)
+mtf_delete_file_in_place (struct mtf_proc *mtf, struct mtf_file **file)
 {
   struct mtf_file *f = *file;
 
@@ -1023,72 +1105,73 @@ mtf_delete_file_in_place (struct mtf_file **file)
     f->prev->next = f->next;
   if (f->next)
     f->next->prev = f->prev;
-  if (f == mtf_head)
-    mtf_head = f->next;
-  if (f == mtf_tail)
-    mtf_tail = f->prev;
+  if (f == mtf->head)
+    mtf->head = f->next;
+  if (f == mtf->tail)
+    mtf->tail = f->prev;
   *file = f->next;
 
   {
     int i;
 
-    for (i = 0; i < f->dict->nvar; i++)
+    for (i = 0; i < dict_get_var_cnt (f->dict); i++)
       {
-       struct variable *v = f->dict->var[i];
+       struct variable *v = dict_get_var (f->dict, i);
+        union value *out = case_data_rw (mtf->mtf_case, get_master (v)->fv);
          
        if (v->type == NUMERIC)
-         compaction_case->data[v->p.mtf.master->fv].f = SYSMIS;
+          out->f = SYSMIS;
        else
-         memset (compaction_case->data[v->p.mtf.master->fv].s, ' ',
-                 v->width);
+         memset (out->s, ' ', v->width);
       }
   }
-  
+
   mtf_free_file (f);
 }
 
 /* Read a record from every input file except the active file. */
 static void
-mtf_read_nonactive_records (void)
+mtf_read_nonactive_records (void *mtf_ UNUSED)
 {
+  struct mtf_proc *mtf = mtf_;
   struct mtf_file *iter;
 
-  for (iter = mtf_head; iter; )
+  for (iter = mtf->head; iter; )
     {
       if (iter->handle)
        {
-         assert (iter->input == NULL);
-         iter->input = xmalloc (sizeof *iter->input * iter->dict->nval);
-         
-         if (!sfm_read_case (iter->handle, iter->input, iter->dict))
-           mtf_delete_file_in_place (&iter);
+         if (!sfm_read_case (iter->reader, &iter->input))
+           mtf_delete_file_in_place (mtf, &iter);
          else
            iter = iter->next;
        }
       else
-       {
-         iter->input = temp_case->data;
-         iter = iter->next;
-       }
+        iter = iter->next;
     }
 }
 
 /* Compare the BY variables for files A and B; return -1 if A < B, 0
    if A == B, 1 if A > B. */
 static inline int
-mtf_compare_BY_values (struct mtf_file *a, struct mtf_file *b)
+mtf_compare_BY_values (struct mtf_proc *mtf,
+                       struct mtf_file *a, struct mtf_file *b,
+                       struct ccase *c)
 {
+  struct ccase *a_input, *b_input;
   int i;
-  
-  for (i = 0; i < mtf_n_by; i++)
+
+  assert ((a == NULL) + (b == NULL) + (c == NULL) <= 1);
+  a_input = case_is_null (&a->input) ? c : &a->input;
+  b_input = case_is_null (&b->input) ? c : &b->input;
+  for (i = 0; i < mtf->by_cnt; i++)
     {
       assert (a->by[i]->type == b->by[i]->type);
       assert (a->by[i]->width == b->by[i]->width);
       
       if (a->by[i]->type == NUMERIC)
        {
-         double af = a->input[a->by[i]->fv].f;
-         double bf = b->input[b->by[i]->fv].f;
+         double af = case_num (a_input, a->by[i]->fv);
+         double bf = case_num (b_input, b->by[i]->fv);
 
          if (af < bf)
            return -1;
@@ -1100,8 +1183,8 @@ mtf_compare_BY_values (struct mtf_file *a, struct mtf_file *b)
          int result;
          
          assert (a->by[i]->type == ALPHA);
-         result = memcmp (a->input[a->by[i]->fv].s,
-                          b->input[b->by[i]->fv].s,
+         result = memcmp (case_str (a_input, a->by[i]->fv),
+                          case_str (b_input, b->by[i]->fv),
                           a->by[i]->width);
          if (result < 0)
            return -1;
@@ -1112,22 +1195,14 @@ mtf_compare_BY_values (struct mtf_file *a, struct mtf_file *b)
   return 0;
 }
 
-/* Used to determine whether we've already initialized this
-   variable. */
-static int mtf_seq_no = 0;
-
 /* Perform one iteration of steps 3...7 above. */
 static int
-mtf_processing (struct ccase *c unused)
+mtf_processing (struct ccase *c, void *mtf_ UNUSED)
 {
-  /* List of files with minimum BY values. */
-  struct mtf_file *min_head, *min_tail;
-
-  /* List of files with non-minimum BY values. */
-  struct mtf_file *max_head, *max_tail;
-
-  /* Iterator. */
-  struct mtf_file *iter;
+  struct mtf_proc *mtf = mtf_;
+  struct mtf_file *min_head, *min_tail; /* Files with minimum BY values. */
+  struct mtf_file *max_head, *max_tail; /* Files with non-minimum BY values. */
+  struct mtf_file *iter;                /* Iterator. */
 
   for (;;)
     {
@@ -1135,7 +1210,7 @@ mtf_processing (struct ccase *c unused)
         return because that would cause a record to be skipped. */
       int advance = 1;
 
-      if (mtf_head->type == MTF_TABLE)
+      if (mtf->head->type == MTF_TABLE)
        return 0;
       
       /* 3. Find the FILE input record with minimum BY values.  Store
@@ -1144,11 +1219,11 @@ mtf_processing (struct ccase *c unused)
         4. Find all the FILE input records with BY values identical
         to the minimums.  Store all the values from these input
         records into the output record. */
-      min_head = min_tail = mtf_head;
+      min_head = min_tail = mtf->head;
       max_head = max_tail = NULL;
-      for (iter = mtf_head->next; iter && iter->type == MTF_FILE;
+      for (iter = mtf->head->next; iter && iter->type == MTF_FILE;
           iter = iter->next)
-       switch (mtf_compare_BY_values (min_head, iter))
+       switch (mtf_compare_BY_values (mtf, min_head, iter, c))
          {
          case -1:
            if (max_head)
@@ -1193,7 +1268,7 @@ mtf_processing (struct ccase *c unused)
            advance = 0;
 
        again:
-         switch (mtf_compare_BY_values (min_head, iter))
+         switch (mtf_compare_BY_values (mtf, min_head, iter, c))
            {
            case -1:
              if (max_head)
@@ -1209,9 +1284,9 @@ mtf_processing (struct ccase *c unused)
            case 1:
              if (iter->handle == NULL)
                return 1;
-             if (sfm_read_case (iter->handle, iter->input, iter->dict))
+             if (sfm_read_case (iter->reader, &iter->input))
                goto again;
-             mtf_delete_file_in_place (&iter);
+             mtf_delete_file_in_place (mtf, &iter);
              break;
 
            default:
@@ -1222,7 +1297,7 @@ mtf_processing (struct ccase *c unused)
        }
 
       /* Next sequence number. */
-      mtf_seq_no++;
+      mtf->seq_num++;
   
       /* Store data to all the records we are using. */
       if (min_tail)
@@ -1231,29 +1306,24 @@ mtf_processing (struct ccase *c unused)
        {
          int i;
 
-         for (i = 0; i < iter->dict->nvar; i++)
+         for (i = 0; i < dict_get_var_cnt (iter->dict); i++)
            {
-             struct variable *v = iter->dict->var[i];
+             struct variable *v = dict_get_var (iter->dict, i);
+              struct ccase *record;
+              union value *out;
          
-             if (v->p.mtf.master->foo == mtf_seq_no)
+             if (mtf->seq_nums[get_master (v)->index] == mtf->seq_num)
                continue;
-             v->p.mtf.master->foo = mtf_seq_no;
-
-#if 0
-             printf ("%s/%s: dest-fv=%d, src-fv=%d\n",
-                     fh_handle_name (iter->handle),
-                     v->name,
-                     v->p.mtf.master->fv, v->fv);
-#endif
+              mtf->seq_nums[get_master (v)->index] = mtf->seq_num;
+
+              record = case_is_null (&iter->input) ? c : &iter->input;
+
+              assert (v->type == NUMERIC || v->type == ALPHA);
+              out = case_data_rw (mtf->mtf_case, get_master (v)->fv);
              if (v->type == NUMERIC)
-               compaction_case->data[v->p.mtf.master->fv].f
-                 = iter->input[v->fv].f;
+               out->f = case_num (record, v->fv);
              else
-               {
-                 assert (v->type == ALPHA);
-                 memcpy (compaction_case->data[v->p.mtf.master->fv].s,
-                         iter->input[v->fv].s, v->width);
-               }
+                memcpy (out->s, case_str (record, v->fv), v->width);
            }
        }
 
@@ -1264,25 +1334,20 @@ mtf_processing (struct ccase *c unused)
        {
          int i;
 
-         for (i = 0; i < iter->dict->nvar; i++)
+         for (i = 0; i < dict_get_var_cnt (iter->dict); i++)
            {
-             struct variable *v = iter->dict->var[i];
+             struct variable *v = dict_get_var (iter->dict, i);
+              union value *out;
          
-             if (v->p.mtf.master->foo == mtf_seq_no)
+             if (mtf->seq_nums[get_master (v)->index] == mtf->seq_num)
                continue;
-             v->p.mtf.master->foo = mtf_seq_no;
-
-#if 0
-             printf ("%s/%s: dest-fv=%d\n",
-                     fh_handle_name (iter->handle),
-                     v->name,
-                     v->p.mtf.master->fv);
-#endif
+              mtf->seq_nums[get_master (v)->index] = mtf->seq_num;
+
+              out = case_data_rw (mtf->mtf_case, get_master (v)->fv);
              if (v->type == NUMERIC)
-               compaction_case->data[v->p.mtf.master->fv].f = SYSMIS;
+                out->f = SYSMIS;
              else
-                memset (compaction_case->data[v->p.mtf.master->fv].s, ' ',
-                        v->width);
+                memset (out->s, ' ', v->width);
            }
 
          if (iter->handle == NULL)
@@ -1290,7 +1355,7 @@ mtf_processing (struct ccase *c unused)
        }
 
       /* 6. Write the output record. */
-      process_active_file_output_case ();
+      mtf->sink->class->write (mtf->sink, mtf->mtf_case);
 
       /* 7. Read another record from each input file FILE and TABLE
         that we stored values from above.  If we come to the end of
@@ -1300,12 +1365,10 @@ mtf_processing (struct ccase *c unused)
        {
          struct mtf_file *next = iter->next_min;
          
-         if (iter->handle)
+         if (iter->reader != NULL)
            {
-             assert (iter->input != NULL);
-
-             if (!sfm_read_case (iter->handle, iter->input, iter->dict))
-               mtf_delete_file_in_place (&iter);
+             if (!sfm_read_case (iter->reader, &iter->input))
+               mtf_delete_file_in_place (mtf, &iter);
            }
 
          iter = next;
@@ -1315,91 +1378,124 @@ mtf_processing (struct ccase *c unused)
        break;
     }
 
-  return (mtf_head && mtf_head->type != MTF_TABLE);
+  return (mtf->head && mtf->head->type != MTF_TABLE);
 }
 
-/* Merge the dictionary for file F into the master dictionary
-   mtf_master. */
+/* Merge the dictionary for file F into master dictionary M. */
 static int
-mtf_merge_dictionary (struct mtf_file *f)
+mtf_merge_dictionary (struct dictionary *const m, struct mtf_file *f)
 {
-  struct dictionary *const m = mtf_master;
   struct dictionary *d = f->dict;
-      
-  if (d->label && m->label == NULL)
-    m->label = xstrdup (d->label);
+  const char *d_docs, *m_docs;
+
+  if (dict_get_label (m) == NULL)
+    dict_set_label (m, dict_get_label (d));
 
-  if (d->documents)
+  d_docs = dict_get_documents (d);
+  m_docs = dict_get_documents (m);
+  if (d_docs != NULL) 
     {
-      m->documents = xrealloc (m->documents,
-                              80 * (m->n_documents + d->n_documents));
-      memcpy (&m->documents[80 * m->n_documents],
-             d->documents, 80 * d->n_documents);
-      m->n_documents += d->n_documents;
+      if (m_docs == NULL)
+        dict_set_documents (m, d_docs);
+      else
+        {
+          char *new_docs;
+          size_t new_len;
+
+          new_len = strlen (m_docs) + strlen (d_docs);
+          new_docs = xmalloc (new_len + 1);
+          strcpy (new_docs, m_docs);
+          strcat (new_docs, d_docs);
+          dict_set_documents (m, new_docs);
+          free (new_docs);
+        }
     }
-      
+  
+  dict_compact_values (d);
+
   {
     int i;
 
-    d->nval = 0;
-    for (i = 0; i < d->nvar; i++)
+    for (i = 0; i < dict_get_var_cnt (d); i++)
       {
-       struct variable *dv = d->var[i];
-       struct variable *mv = find_dict_variable (m, dv->name);
+       struct variable *dv = dict_get_var (d, i);
+       struct variable *mv = dict_lookup_var (m, dv->name);
 
-       dv->fv = d->nval;
-       d->nval += dv->nv;
-       
        assert (dv->type == ALPHA || dv->width == 0);
        assert (!mv || mv->type == ALPHA || mv->width == 0);
        if (mv && dv->width == mv->width)
          {
-           if (dv->val_lab && !mv->val_lab)
-             mv->val_lab = copy_value_labels (dv->val_lab);
-           if (dv->miss_type != MISSING_NONE && mv->miss_type == MISSING_NONE)
+           if (val_labs_count (dv->val_labs)
+                && !val_labs_count (mv->val_labs))
+             mv->val_labs = val_labs_copy (dv->val_labs);
+           if (dv->miss_type != MISSING_NONE
+                && mv->miss_type == MISSING_NONE)
              copy_missing_values (mv, dv);
          }
        if (mv && dv->label && !mv->label)
          mv->label = xstrdup (dv->label);
-       if (!mv)
-         {
-           mv = force_dup_variable (m, dv, dv->name);
-
-           /* Used to make sure we initialize each variable in the
-              master dictionary exactly once per case. */
-           mv->foo = mtf_seq_no;
-         }
+       if (!mv) 
+          {
+            mv = dict_clone_var (m, dv, dv->name, dv->longname);
+            assert (mv != NULL);
+          }
        else if (mv->width != dv->width)
          {
            msg (SE, _("Variable %s in file %s (%s) has different "
                       "type or width from the same variable in "
                       "earlier file (%s)."),
-                dv->name, fh_handle_name (f->handle),
+                dv->name, handle_get_name (f->handle),
                 var_type_description (dv), var_type_description (mv));
            return 0;
          }
-       dv->p.mtf.master = mv;
+        set_master (dv, mv);
       }
   }
 
   return 1;
 }
+
+/* Marks V's master variable as MASTER. */
+static void
+set_master (struct variable *v, struct variable *master) 
+{
+  var_attach_aux (v, master, NULL);
+}
+
+/* Returns the master variable corresponding to V,
+   as set with set_master(). */
+static struct variable *
+get_master (struct variable *v) 
+{
+  assert (v->aux != NULL);
+  return v->aux;
+}
 \f
 /* IMPORT command. */
 
+/* IMPORT input program. */
+struct import_pgm 
+  {
+    struct pfm_reader *reader;  /* Portable file reader. */
+    struct case_map *map;       /* Map from system file to active file dict. */
+    struct ccase bounce;        /* Bounce buffer. */
+  };
+
+static void import_pgm_free (struct import_pgm *);
+
 /* Parses the IMPORT command. */
 int
 cmd_import (void)
 {
-  struct file_handle *handle = NULL;
-  struct dictionary *dict;
-  int options = GTSV_OPT_NONE;
+  struct import_pgm *pgm = NULL;
+  struct file_handle *fh = NULL;
+  struct dictionary *dict = NULL;
   int type;
 
-  int i;
-  int nval;
-
-  lex_match_id ("IMPORT");
+  pgm = xmalloc (sizeof *pgm);
+  pgm->reader = NULL;
+  pgm->map = NULL;
+  case_nullify (&pgm->bounce);
 
   for (;;)
     {
@@ -1409,8 +1505,8 @@ cmd_import (void)
        {
          lex_match ('=');
 
-         handle = fh_parse_file_handle ();
-         if (handle == NULL)
+         fh = fh_parse ();
+         if (fh == NULL)
            return CMD_FAILURE;
        }
       else if (lex_match_id ("TYPE"))
@@ -1437,162 +1533,206 @@ cmd_import (void)
 
   discard_variables ();
 
-  dict = pfm_read_dictionary (handle, NULL);
-  if (dict == NULL)
+  pgm->reader = pfm_open_reader (fh, &dict, NULL);
+  if (pgm->reader == NULL)
     return CMD_FAILURE;
+  case_create (&pgm->bounce, dict_get_next_value_idx (dict));
+  
+  start_case_map (dict);
+  if (!trim_dictionary (dict, OP_READ, NULL))
+    goto error;
+  pgm->map = finish_case_map (dict);
+  
+  dict_destroy (default_dict);
+  default_dict = dict;
 
-#if DEBUGGING
-  dump_dict_variables (dict);
-#endif
-  if (0 == trim_dictionary (dict, &options))
-    {
-      fh_close_handle (handle);
-      return CMD_FAILURE;
-    }
-#if DEBUGGING
-  dump_dict_variables (dict);
-#endif
-
-  /* Set the fv and lv elements of all variables remaining in the
-     dictionary. */
-  nval = 0;
-  for (i = 0; i < dict->nvar; i++)
-    {
-      struct variable *v = dict->var[i];
+  vfm_source = create_case_source (&import_source_class, pgm);
 
-      v->fv = nval;
-      nval += v->nv;
-    }
-  dict->nval = nval;
-  assert (nval);
+  return CMD_SUCCESS;
 
-#if DEBUGGING
-  printf (_("IMPORT translation table from file to memory:\n"));
-  for (i = 0; i < dict->nvar; i++)
-    {
-      struct variable *v = dict->var[i];
+ error:
+  import_pgm_free (pgm);
+  if (dict != NULL)
+    dict_destroy (dict);
+  return CMD_FAILURE;
+}
 
-      printf (_("  %8s from %3d,%3d to %3d,%3d\n"), v->name,
-             v->get.fv, v->get.nv, v->fv, v->nv);
+/* Frees a struct import_pgm. */
+static void
+import_pgm_free (struct import_pgm *pgm) 
+{
+  if (pgm != NULL) 
+    {
+      pfm_close_reader (pgm->reader);
+      destroy_case_map (pgm->map);
+      case_destroy (&pgm->bounce);
+      free (pgm);
     }
-#endif
-
-  restore_dictionary (dict);
-
-  vfm_source = &import_source;
-  get_file = handle;
+}
 
-  return CMD_SUCCESS;
+/* Clears internal state related to IMPORT input procedure. */
+static void
+import_source_destroy (struct case_source *source)
+{
+  struct import_pgm *pgm = source->aux;
+  import_pgm_free (pgm);
 }
 
-/* Reads all the cases from the data file and passes them to
-   write_case(). */
+/* Reads all the cases from the data file into C and passes them
+   to WRITE_CASE one by one, passing WC_DATA. */
 static void
-import_source_read (void)
+import_source_read (struct case_source *source,
+                 struct ccase *c,
+                 write_case_func *write_case, write_case_data wc_data)
 {
-  while (pfm_read_case (get_file, temp_case->data, &default_dict)
-        && write_case ())
-    ;
-  get_source_destroy_source ();
+  struct import_pgm *pgm = source->aux;
+  int ok;
+
+  do
+    {
+      if (pgm->map == NULL)
+        ok = pfm_read_case (pgm->reader, c);
+      else
+        {
+          ok = pfm_read_case (pgm->reader, &pgm->bounce);
+          if (ok)
+            map_case (pgm->map, &pgm->bounce, c);
+        }
+
+      if (ok)
+        ok = write_case (wc_data);
+    }
+  while (ok);
 }
 
-struct case_stream import_source =
+const struct case_source_class import_source_class =
   {
+    "IMPORT",
     NULL,
     import_source_read,
-    NULL,
-    NULL,
-    get_source_destroy_source,
-    NULL,
-    "IMPORT",
+    import_source_destroy,
   };
-\f
-static int export_write_case_func (struct ccase *c);
-     
-/* Parses the EXPORT command.  */
-/* FIXME: same as cmd_save_internal(). */
-int
-cmd_export (void)
-{
-  struct file_handle *handle;
-  struct dictionary *dict;
-  int options = GTSV_OPT_SAVE;
 
-  struct save_trns *t;
-
-  int i;
+\f
+/* Case map.
 
-  lex_match_id ("EXPORT");
+   A case map copies data from a case that corresponds for one
+   dictionary to a case that corresponds to a second dictionary
+   derived from the first by, optionally, deleting, reordering,
+   or renaming variables.  (No new variables may be created.)
+   */
 
-  lex_match ('/');
-  if (lex_match_id ("OUTFILE"))
-    lex_match ('=');
+/* A case map. */
+struct case_map
+  {
+    size_t value_cnt;   /* Number of values in map. */
+    int *map;           /* For each destination index, the
+                           corresponding source index. */
+  };
 
-  handle = fh_parse_file_handle ();
-  if (handle == NULL)
-    return CMD_FAILURE;
+/* Prepares dictionary D for producing a case map.  Afterward,
+   the caller may delete, reorder, or rename variables within D
+   at will before using finish_case_map() to produce the case
+   map.
 
-  dict = save_dictionary ();
-#if DEBUGGING
-  dump_dict_variables (dict);
-#endif
-  for (i = 0; i < dict->nvar; i++)
-    dict->var[i]->foo = i;
-  if (0 == trim_dictionary (dict, &options))
+   Uses D's aux members, which may not otherwise be in use. */
+static void
+start_case_map (struct dictionary *d) 
+{
+  size_t var_cnt = dict_get_var_cnt (d);
+  size_t i;
+  
+  for (i = 0; i < var_cnt; i++)
     {
-      fh_close_handle (handle);
-      return CMD_FAILURE;
+      struct variable *v = dict_get_var (d, i);
+      int *src_fv = xmalloc (sizeof *src_fv);
+      *src_fv = v->fv;
+      var_attach_aux (v, src_fv, var_dtor_free);
     }
+}
 
-#if DEBUGGING
-  dump_dict_variables (dict);
-#endif
+/* Produces a case map from dictionary D, which must have been
+   previously prepared with start_case_map().
 
-  /* Write dictionary. */
-  if (!pfm_write_dictionary (handle, dict))
+   Does not retain any reference to D, and clears the aux members
+   set up by start_case_map().
+
+   Returns the new case map, or a null pointer if no mapping is
+   required (that is, no data has changed position). */
+static struct case_map *
+finish_case_map (struct dictionary *d) 
+{
+  struct case_map *map;
+  size_t var_cnt = dict_get_var_cnt (d);
+  size_t i;
+  int identity_map;
+
+  map = xmalloc (sizeof *map);
+  map->value_cnt = dict_get_next_value_idx (d);
+  map->map = xmalloc (sizeof *map->map * map->value_cnt);
+  for (i = 0; i < map->value_cnt; i++)
+    map->map[i] = -1;
+
+  identity_map = 1;
+  for (i = 0; i < var_cnt; i++) 
     {
-      free_dictionary (dict);
-      fh_close_handle (handle);
-      return CMD_FAILURE;
+      struct variable *v = dict_get_var (d, i);
+      int *src_fv = (int *) var_detach_aux (v);
+      size_t idx;
+
+      if (v->fv != *src_fv)
+        identity_map = 0;
+      
+      for (idx = 0; idx < v->nv; idx++)
+        {
+          int src_idx = *src_fv + idx;
+          int dst_idx = v->fv + idx;
+          
+          assert (map->map[dst_idx] == -1);
+          map->map[dst_idx] = src_idx;
+        }
+      free (src_fv);
     }
 
-  /* Fill in transformation structure. */
-  t = trns = xmalloc (sizeof *t);
-  t->h.proc = save_trns_proc;
-  t->h.free = save_trns_free;
-  t->f = handle;
-  t->nvar = dict->nvar;
-  t->var = xmalloc (sizeof *t->var * dict->nvar);
-  for (i = 0; i < dict->nvar; i++)
-    t->var[i] = dict->var[i]->foo;
-  t->case_buf = xmalloc (sizeof *t->case_buf * dict->nvar);
-  free_dictionary (dict);
+  if (identity_map) 
+    {
+      destroy_case_map (map);
+      return NULL;
+    }
 
-  procedure (NULL, export_write_case_func, NULL);
-  save_trns_free ((struct trns_header *) t);
+  while (map->value_cnt > 0 && map->map[map->value_cnt - 1] == -1)
+    map->value_cnt--;
 
-  return CMD_SUCCESS;
+  return map;
 }
 
-static int
-export_write_case_func (struct ccase *c)
+/* Maps from SRC to DST, applying case map MAP. */
+static void
+map_case (const struct case_map *map,
+          const struct ccase *src, struct ccase *dst) 
 {
-  union value *p = (union value *) trns->case_buf;
-  int i;
+  size_t dst_idx;
 
-  for (i = 0; i < trns->nvar; i++)
-    {
-      struct variable *v = default_dict.var[trns->var[i]];
+  assert (map != NULL);
+  assert (src != NULL);
+  assert (dst != NULL);
+  assert (src != dst);
 
-      if (v->type == NUMERIC)
-       *p++ = c->data[v->fv];
-      else
-       (*p++).c = c->data[v->fv].s;
+  for (dst_idx = 0; dst_idx < map->value_cnt; dst_idx++)
+    {
+      int src_idx = map->map[dst_idx];
+      if (src_idx != -1)
+        *case_data_rw (dst, dst_idx) = *case_data (src, src_idx);
     }
+}
 
-  printf (".");
-  fflush (stdout);
-  
-  pfm_write_case (trns->f, (union value *) trns->case_buf);
-  return 1;
+/* Destroys case map MAP. */
+static void
+destroy_case_map (struct case_map *map) 
+{
+  if (map != NULL) 
+    {
+      free (map->map);
+      free (map);
+    }
 }