Add scratch file handles.
[pspp-builds.git] / src / get.c
index 8aecc386e9130df42ce109a49b87817db4b54529..deda2d57b6c6fd29de6e72a70d49575360178912 100644 (file)
--- a/src/get.c
+++ b/src/get.c
@@ -1,5 +1,5 @@
 /* PSPP - computes sample statistics.
-   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+   Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
    Written by Ben Pfaff <blp@gnu.org>.
 
    This program is free software; you can redistribute it and/or
 
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
-   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-   02111-1307, USA. */
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
 
 #include <config.h>
-#include <assert.h>
+#include "error.h"
 #include <stdlib.h>
 #include "alloc.h"
+#include "any-reader.h"
+#include "any-writer.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-write.h"
 #include "settings.h"
-#include "sfm.h"
+#include "sfm-write.h"
 #include "str.h"
 #include "value-labels.h"
 #include "var.h"
 #include "vfm.h"
 #include "vfmP.h"
 
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
 #include "debug-print.h"
 
-/* XSAVE transformation (and related SAVE, EXPORT procedures). */
-struct save_trns
-  {
-    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. */
-  };
+/* 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 *);
 
-/* 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
+static bool parse_dict_trim (struct dictionary *);
+\f
+/* Reading system and portable files. */
 
-/* The file being read by the input program. */
-static struct file_handle *get_file;
+/* Type of command. */
+enum reader_command 
+  {
+    GET_CMD,
+    IMPORT_CMD
+  };
 
-/* The transformation being used by the SAVE procedure. */
-static struct save_trns *trns;
+/* Case reader input program. */
+struct case_reader_pgm 
+  {
+    struct any_reader *reader;  /* File reader. */
+    struct case_map *map;       /* Map from file dict to active file dict. */
+    struct ccase bounce;        /* Bounce buffer. */
+  };
 
-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 const struct case_source_class case_reader_source_class;
 
-#if DEBUGGING
-void dump_dict_variables (struct dictionary *);
-#endif
+static void case_reader_pgm_free (struct case_reader_pgm *);
 
-/* Parses the GET command. */
-int
-cmd_get (void)
+/* Parses a GET or IMPORT command. */
+static int
+parse_read_command (enum reader_command type)
 {
-  struct file_handle *handle;
-  struct dictionary *dict;
-  int options = GTSV_OPT_NONE;
-
-  int i;
-  int nval;
-
-  lex_match_id ("GET");
-  discard_variables ();
+  struct case_reader_pgm *pgm = NULL;
+  struct file_handle *fh = NULL;
+  struct dictionary *dict = NULL;
 
-  lex_match ('/');
-  if (lex_match_id ("FILE"))
-    lex_match ('=');
+  for (;;)
+    {
+      lex_match ('/');
 
-  handle = fh_parse_file_handle ();
-  if (handle == NULL)
-    return CMD_FAILURE;
+      if (lex_match_id ("FILE") || token == T_STRING)
+       {
+         lex_match ('=');
 
-  dict = sfm_read_dictionary (handle, NULL);
-  if (dict == NULL)
-    return CMD_FAILURE;
+         fh = fh_parse (FH_REF_FILE | FH_REF_SCRATCH);
+         if (fh == NULL)
+            goto error;
+       }
+      else if (type == IMPORT_CMD && lex_match_id ("TYPE"))
+       {
+         lex_match ('=');
 
-#if DEBUGGING
-  dump_dict_variables (dict);
-#endif
-  if (0 == trim_dictionary (dict, &options))
-    {
-      fh_close_handle (handle);
-      return CMD_FAILURE;
+         if (lex_match_id ("COMM"))
+           type = PFM_COMM;
+         else if (lex_match_id ("TAPE"))
+           type = PFM_TAPE;
+         else
+           {
+             lex_error (_("expecting COMM or TAPE"));
+              goto error;
+           }
+       }
+      else
+        break; 
     }
-#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++)
+  
+  if (fh == NULL) 
     {
-      struct variable *v = dict->var[i];
-
-      v->fv = nval;
-      nval += v->nv;
+      lex_sbc_missing ("FILE");
+      goto error;
     }
-  dict->nval = nval;
-  assert (nval);
+              
+  discard_variables ();
 
-#if DEBUGGING
-  printf (_("GET translation table from file to memory:\n"));
-  for (i = 0; i < dict->nvar; i++)
-    {
-      struct variable *v = dict->var[i];
+  pgm = xmalloc (sizeof *pgm);
+  pgm->reader = any_reader_open (fh, &dict);
+  pgm->map = NULL;
+  case_nullify (&pgm->bounce);
+  if (pgm->reader == NULL)
+    goto error;
 
-      printf (_("  %8s from %3d,%3d to %3d,%3d\n"), v->name,
-             v->get.fv, v->get.nv, v->fv, v->nv);
+  case_create (&pgm->bounce, dict_get_next_value_idx (dict));
+  
+  start_case_map (dict);
+
+  while (token != '.')
+    {
+      lex_match ('/');
+      if (!parse_dict_trim (dict))
+        goto error;
     }
-#endif
 
-  restore_dictionary (dict);
+  pgm->map = finish_case_map (dict);
+  
+  dict_destroy (default_dict);
+  default_dict = dict;
 
-  vfm_source = &get_source;
-  get_file = handle;
+  vfm_source = create_case_source (&case_reader_source_class, pgm);
 
   return CMD_SUCCESS;
+
+ error:
+  case_reader_pgm_free (pgm);
+  if (dict != NULL)
+    dict_destroy (dict);
+  return CMD_FAILURE;
 }
 
-/* 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)
+/* Frees a struct case_reader_pgm. */
+static void
+case_reader_pgm_free (struct case_reader_pgm *pgm) 
 {
-  struct file_handle *handle;
-  struct dictionary *dict;
-  int options = GTSV_OPT_SAVE;
-
-  struct save_trns *t;
-  struct sfm_write_info inf;
-
-  int i;
-
-  lex_match_id ("SAVE");
-
-  lex_match ('/');
-  if (lex_match_id ("OUTFILE"))
-    lex_match ('=');
-
-  handle = fh_parse_file_handle ();
-  if (handle == NULL)
-    return CMD_FAILURE;
-
-  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))
+  if (pgm != NULL) 
     {
-      fh_close_handle (handle);
-      return CMD_FAILURE;
+      any_reader_close (pgm->reader);
+      destroy_case_map (pgm->map);
+      case_destroy (&pgm->bounce);
+      free (pgm);
     }
+}
 
-#if DEBUGGING
-  dump_dict_variables (dict);
-#endif
+/* Clears internal state related to case reader input procedure. */
+static void
+case_reader_source_destroy (struct case_source *source)
+{
+  struct case_reader_pgm *pgm = source->aux;
+  case_reader_pgm_free (pgm);
+}
 
-  /* Write dictionary. */
-  inf.h = handle;
-  inf.dict = dict;
-  inf.compress = !!(options & GTSV_OPT_COMPRESSED);
-  if (!sfm_write_dictionary (&inf))
-    {
-      free_dictionary (dict);
-      fh_close_handle (handle);
-      return CMD_FAILURE;
-    }
+/* Reads all the cases from the data file into C and passes them
+   to WRITE_CASE one by one, passing WC_DATA. */
+static void
+case_reader_source_read (struct case_source *source,
+                    struct ccase *c,
+                    write_case_func *write_case, write_case_data wc_data)
+{
+  struct case_reader_pgm *pgm = source->aux;
+  int ok;
 
-  /* 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. */
+  do
     {
-      procedure (NULL, save_write_case_func, NULL);
-      save_trns_free ((struct trns_header *) t);
+      if (pgm->map == NULL)
+        ok = any_reader_read (pgm->reader, c);
+      else
+        {
+          ok = any_reader_read (pgm->reader, &pgm->bounce);
+          if (ok)
+            map_case (pgm->map, &pgm->bounce, c);
+        }
+
+      if (ok)
+        ok = write_case (wc_data);
     }
-  else
-    /* XSAVE. */
-    add_transformation ((struct trns_header *) t);
-
-  return CMD_SUCCESS;
+  while (ok);
 }
 
-/* Parses and performs the SAVE procedure. */
+static const struct case_source_class case_reader_source_class =
+  {
+    "case reader",
+    NULL,
+    case_reader_source_read,
+    case_reader_source_destroy,
+  };
+\f
+/* GET. */
 int
-cmd_save (void)
+cmd_get (void) 
 {
-  return cmd_save_internal (0);
+  return parse_read_command (GET_CMD);
 }
 
-/* Parses the XSAVE transformation command. */
+/* IMPORT. */
 int
-cmd_xsave (void)
+cmd_import (void) 
 {
-  return cmd_save_internal (1);
+  return parse_read_command (IMPORT_CMD);
 }
+\f
+/* Writing system and portable files. */ 
 
-static int
-save_write_case_func (struct ccase * c)
+/* Type of output file. */
+enum writer_type
+  {
+    SYSFILE_WRITER,     /* System file. */
+    PORFILE_WRITER      /* Portable file. */
+  };
+
+/* Type of a command. */
+enum command_type 
+  {
+    XFORM_CMD,          /* Transformation. */
+    PROC_CMD            /* Procedure. */
+  };
+
+/* File writer plus a case map. */
+struct case_writer
+  {
+    struct any_writer *writer;  /* File writer. */
+    struct case_map *map;       /* Map to output file dictionary
+                                   (null pointer for identity mapping). */
+    struct ccase bounce;        /* Bounce buffer for mapping (if needed). */
+  };
+
+/* Destroys AW. */
+static void
+case_writer_destroy (struct case_writer *aw)
 {
-  save_trns_proc ((struct trns_header *) trns, c);
-  return 1;
+  if (aw != NULL) 
+    {
+      any_writer_close (aw->writer);
+      destroy_case_map (aw->map);
+      case_destroy (&aw->bounce);
+      free (aw);
+    }
 }
 
-static int
-save_trns_proc (struct trns_header * t unused, struct ccase * c)
+/* Parses SAVE or XSAVE or EXPORT or XEXPORT command.
+   WRITER_TYPE identifies the type of file to write,
+   and COMMAND_TYPE identifies the type of command.
+
+   On success, returns a writer.
+   For procedures only, sets *RETAIN_UNSELECTED to true if cases
+   that would otherwise be excluded by FILTER or USE should be
+   included.
+
+   On failure, returns a null pointer. */
+static struct case_writer *
+parse_write_command (enum writer_type writer_type,
+                     enum command_type command_type,
+                     bool *retain_unselected)
 {
-  flt64 *p = trns->case_buf;
-  int i;
+  /* Common data. */
+  struct file_handle *handle; /* Output file. */
+  struct dictionary *dict;    /* Dictionary for output file. */
+  struct case_writer *aw;      /* Writer. */  
+
+  /* Common options. */
+  bool print_map;             /* Print map?  TODO. */
+  bool print_short_names;     /* Print long-to-short name map.  TODO. */
+  struct sfm_write_options sysfile_opts;
+  struct pfm_write_options porfile_opts;
+
+  assert (writer_type == SYSFILE_WRITER || writer_type == PORFILE_WRITER);
+  assert (command_type == XFORM_CMD || command_type == PROC_CMD);
+  assert ((retain_unselected != NULL) == (command_type == PROC_CMD));
+
+  if (command_type == PROC_CMD)
+    *retain_unselected = true;
+
+  handle = NULL;
+  dict = dict_clone (default_dict);
+  aw = xmalloc (sizeof *aw);
+  aw->writer = NULL;
+  aw->map = NULL;
+  case_nullify (&aw->bounce);
+  print_map = false;
+  print_short_names = false;
+  sysfile_opts = sfm_writer_default_options ();
+  porfile_opts = pfm_writer_default_options ();
+
+  start_case_map (dict);
+  dict_delete_scratch_vars (dict);
 
-  for (i = 0; i < trns->nvar; i++)
+  lex_match ('/');
+  for (;;)
     {
-      struct variable *v = default_dict.var[trns->var[i]];
-      if (v->type == NUMERIC)
+      if (lex_match_id ("OUTFILE"))
        {
-         double src = c->data[v->fv].f;
-         if (src == SYSMIS)
-           *p++ = -FLT64_MAX;
-         else
-           *p++ = src;
+          if (handle != NULL) 
+            {
+              lex_sbc_only_once ("OUTFILE");
+              goto error; 
+            }
+          
+         lex_match ('=');
+      
+         handle = fh_parse (FH_REF_FILE | FH_REF_SCRATCH);
+         if (handle == NULL)
+           goto error;
        }
-      else
+      else if (lex_match_id ("NAMES"))
+        print_short_names = true;
+      else if (lex_match_id ("PERMISSIONS")) 
+        {
+          bool cw;
+          
+          lex_match ('=');
+          if (lex_match_id ("READONLY"))
+            cw = false;
+          else if (lex_match_id ("WRITEABLE"))
+            cw = true;
+          else
+            {
+              lex_error (_("expecting %s or %s"), "READONLY", "WRITEABLE");
+              goto error;
+            }
+          sysfile_opts.create_writeable = porfile_opts.create_writeable = cw;
+        }
+      else if (command_type == PROC_CMD && lex_match_id ("UNSELECTED")) 
+        {
+          lex_match ('=');
+          if (lex_match_id ("RETAIN"))
+            *retain_unselected = true;
+          else if (lex_match_id ("DELETE"))
+            *retain_unselected = false;
+          else
+            {
+              lex_error (_("expecting %s or %s"), "RETAIN", "DELETE");
+              goto error;
+            }
+        }
+      else if (writer_type == SYSFILE_WRITER && lex_match_id ("COMPRESSED"))
+       sysfile_opts.compress = true;
+      else if (writer_type == SYSFILE_WRITER && lex_match_id ("UNCOMPRESSED"))
+       sysfile_opts.compress = false;
+      else if (writer_type == SYSFILE_WRITER && lex_match_id ("VERSION"))
        {
-         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);
+         lex_match ('=');
+         if (!lex_force_int ())
+            goto error;
+          sysfile_opts.version = lex_integer ();
+          lex_get ();
        }
+      else if (writer_type == PORFILE_WRITER && lex_match_id ("TYPE")) 
+        {
+          lex_match ('=');
+          if (lex_match_id ("COMMUNICATIONS"))
+            porfile_opts.type = PFM_COMM;
+          else if (lex_match_id ("TAPE"))
+            porfile_opts.type = PFM_TAPE;
+          else
+            {
+              lex_error (_("expecting %s or %s"), "COMM", "TAPE");
+              goto error;
+            }
+        }
+      else if (writer_type == PORFILE_WRITER && lex_match_id ("DIGITS")) 
+        {
+          lex_match ('=');
+          if (!lex_force_int ())
+            goto error;
+          porfile_opts.digits = lex_integer ();
+          lex_get ();
+        }
+      else if (!parse_dict_trim (dict))
+        goto error;
+      
+      if (!lex_match ('/'))
+       break;
     }
+  if (lex_end_of_command () != CMD_SUCCESS)
+    goto error;
 
-  sfm_write_case (trns->f, trns->case_buf, p - trns->case_buf);
-  return -1;
-}
+  if (handle == NULL) 
+    {
+      lex_sbc_missing ("OUTFILE");
+      goto error;
+    }
 
-static void
-save_trns_free (struct trns_header *pt)
-{
-  struct save_trns *t = (struct save_trns *) pt;
+  dict_compact_values (dict);
+  aw->map = finish_case_map (dict);
+  if (aw->map != NULL)
+    case_create (&aw->bounce, dict_get_next_value_idx (dict));
 
-  fh_close_handle (t->f);
-  free (t->var);
-  free (t->case_buf);
-  free (t);
+  if (fh_get_referent (handle) == FH_REF_FILE) 
+    {
+      switch (writer_type) 
+        {
+        case SYSFILE_WRITER:
+          aw->writer = any_writer_from_sfm_writer (
+            sfm_open_writer (handle, dict, sysfile_opts));
+          break;
+        case PORFILE_WRITER:
+          aw->writer = any_writer_from_pfm_writer (
+            pfm_open_writer (handle, dict, porfile_opts));
+          break;
+        }
+    }
+  else
+    aw->writer = any_writer_open (handle, dict);
+  dict_destroy (dict);
+  
+  return aw;
+
+ error:
+  case_writer_destroy (aw);
+  dict_destroy (dict);
+  return NULL;
 }
 
-/* Deletes NV variables from DICT, starting at index FIRST.  The
-   variables must have consecutive indices.  The variables are cleared
-   and freed. */
+/* Writes case C to writer AW. */
 static void
-dict_delete_run (struct dictionary *dict, int first, int nv)
+case_writer_write_case (struct case_writer *aw, struct ccase *c) 
 {
-  int i;
-
-  for (i = first; i < first + nv; i++)
+  if (aw->map != NULL) 
     {
-      clear_variable (dict, dict->var[i]);
-      free (dict->var[i]);
+      map_case (aw->map, c, &aw->bounce);
+      c = &aw->bounce; 
     }
-  for (i = first; i < dict->nvar - nv; i++)
-    {
-      dict->var[i] = dict->var[i + nv];
-      dict->var[i]->index -= nv;
-    }
-  dict->nvar -= nv;
+  any_writer_write (aw->writer, c);
 }
+\f
+/* SAVE and EXPORT. */
 
-static int rename_variables (struct dictionary * dict);
+static int output_proc (struct ccase *, void *);
 
-/* 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. */
-/* FIXME: IN, FIRST, LAST, MAP. */
+/* Parses and performs the SAVE or EXPORT procedure. */
 static int
-trim_dictionary (struct dictionary *dict, int *options)
+parse_output_proc (enum writer_type writer_type)
 {
-  if (set_scompression)
-    *options |= GTSV_OPT_COMPRESSED;
+  bool retain_unselected;
+  struct variable *saved_filter_variable;
+  struct case_writer *aw;
 
-  if (*options & GTSV_OPT_SAVE)
-    {
-      int i;
+  aw = parse_write_command (writer_type, PROC_CMD, &retain_unselected);
+  if (aw == NULL) 
+    return CMD_FAILURE;
 
-      /* Delete all the scratch variables. */
-      for (i = 0; i < dict->nvar; i++)
-       {
-         int j;
-         
-         if (dict->var[i]->name[0] != '#')
-           continue;
+  saved_filter_variable = dict_get_filter (default_dict);
+  if (retain_unselected) 
+    dict_set_filter (default_dict, NULL);
+  procedure (output_proc, aw);
+  dict_set_filter (default_dict, saved_filter_variable);
 
-         /* Find a run of variables to be deleted. */
-         for (j = i + 1; j < dict->nvar; j++)
-           if (dict->var[j]->name[0] != '#')
-             break;
+  case_writer_destroy (aw);
+  return CMD_SUCCESS;
+}
 
-         /* Actually delete 'em. */
-         dict_delete_run (dict, i, j - i);
-       }
-    }
-  
-  while ((*options & GTSV_OPT_MATCH_FILES) || 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;
-      else if (lex_match_id ("DROP"))
-       {
-         struct variable **v;
-         int nv;
-         int i;
+/* Writes case C to file. */
+static int
+output_proc (struct ccase *c, void *aw_) 
+{
+  struct case_writer *aw = aw_;
+  case_writer_write_case (aw, c);
+  return 0;
+}
 
-         lex_match ('=');
-         if (!parse_variables (dict, &v, &nv, PV_NONE))
-           return 0;
+int
+cmd_save (void) 
+{
+  return parse_output_proc (SYSFILE_WRITER);
+}
 
-         /* Loop through the variables to delete. */
-         for (i = 0; i < nv;)
-           {
-             int j;
+int
+cmd_export (void) 
+{
+  return parse_output_proc (PORFILE_WRITER);
+}
+\f
+/* XSAVE and XEXPORT. */
 
-             /* 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;
+/* Transformation. */
+struct output_trns 
+  {
+    struct case_writer *aw;      /* Writer. */
+  };
 
-             /* Actually delete 'em. */
-             dict_delete_run (dict, v[i]->index, j - i);
-             i = j;
-           }
-       }
-      else if (lex_match_id ("KEEP"))
-       {
-         struct variable **v;
-         int nv;
+static trns_proc_func output_trns_proc;
+static trns_free_func output_trns_free;
 
-         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;
-       }
-      else if (lex_match_id ("RENAME"))
-       {
-         if (!rename_variables (dict))
-           return 0;
-       }
-      else
-       {
-         lex_error (_("while expecting a valid subcommand"));
-         return 0;
-       }
+/* Parses the XSAVE or XEXPORT transformation command. */
+static int
+parse_output_trns (enum writer_type writer_type) 
+{
+  struct output_trns *t = xmalloc (sizeof *t);
+  t->aw = parse_write_command (writer_type, XFORM_CMD, NULL);
+  if (t->aw == NULL) 
+    {
+      free (t);
+      return CMD_FAILURE;
+    }
 
-      if (dict->nvar == 0)
-       {
-         msg (SE, _("All variables deleted from system file dictionary."));
-         return 0;
-       }
+  add_transformation (output_trns_proc, output_trns_free, t);
+  return CMD_SUCCESS;
+}
+
+/* Writes case C to the system file specified on XSAVE or XEXPORT. */
+static int
+output_trns_proc (void *trns_, struct ccase *c, int case_num UNUSED)
+{
+  struct output_trns *t = trns_;
+  case_writer_write_case (t->aw, c);
+  return -1;
+}
+
+/* Frees an XSAVE or XEXPORT transformation. */
+static void
+output_trns_free (void *trns_)
+{
+  struct output_trns *t = trns_;
 
-      if (*options & GTSV_OPT_MATCH_FILES)
-       return 1;
+  if (t != NULL)
+    {
+      case_writer_destroy (t->aw);
+      free (t);
     }
+}
+
+/* XSAVE command. */
+int
+cmd_xsave (void) 
+{
+  return parse_output_trns (SYSFILE_WRITER);
+}
 
-  if (token != '.')
+/* XEXPORT command. */
+int
+cmd_xexport (void) 
+{
+  return parse_output_trns (PORFILE_WRITER);
+}
+\f
+static bool rename_variables (struct dictionary *dict);
+static bool drop_variables (struct dictionary *dict);
+static bool keep_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.  Returns true on success, false on failure. */
+static bool
+parse_dict_trim (struct dictionary *dict)
+{
+  if (lex_match_id ("MAP")) 
     {
-      lex_error (_("expecting end of command"));
-      return 0;
+      /* FIXME. */
+      return true;
+    }
+  else if (lex_match_id ("DROP"))
+    return drop_variables (dict);
+  else if (lex_match_id ("KEEP"))
+    return keep_variables (dict);
+  else if (lex_match_id ("RENAME"))
+    return rename_variables (dict);
+  else
+    {
+      lex_error (_("expecting a valid subcommand"));
+      return false;
     }
-  
-  return 1;
 }
 
 /* Parses and performs the RENAME subcommand of GET and SAVE. */
-static int
-rename_variables (struct dictionary * dict)
+static bool
+rename_variables (struct dictionary *dict)
 {
-  int i;
+  size_t i;
 
   int success = 0;
 
   struct variable **v;
   char **new_names;
-  int nv, nn;
+  size_t nv, nn;
+  char *err_name;
 
   int group;
 
@@ -464,9 +614,7 @@ rename_variables (struct dictionary * dict)
       if (!lex_force_match ('=')
          || !lex_force_id ())
        return 0;
-      if (!strncmp (tokid, v->name, 8))
-       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 "
@@ -476,7 +624,7 @@ rename_variables (struct dictionary * dict)
          return 0;
        }
       
-      rename_variable (dict, v, tokid);
+      dict_rename_var (dict, v, tokid);
       lex_get ();
       return 1;
     }
@@ -487,46 +635,38 @@ rename_variables (struct dictionary * dict)
   group = 1;
   while (lex_match ('('))
     {
-      int old_nv = nv;
+      size_t 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 "
-              "match number of variables on right side (%d), in "
-              "parenthesized group %d of RENAME subcommand."),
-              nv - old_nv, nn - old_nv, group);
-         goto lossage;
+         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."),
+              (unsigned) (nv - old_nv), (unsigned) (nn - old_nv), group);
+         goto done;
        }
       if (!lex_force_match (')'))
-       goto lossage;
+       goto done;
       group++;
     }
 
-  for (i = 0; i < nv; i++)
-    hsh_force_delete (dict->name_tab, 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 != hsh_insert (dict->name_tab, 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,50 +675,53 @@ lossage:
   return success;
 }
 
-#if DEBUGGING
-void
-dump_dict_variables (struct dictionary * dict)
+/* Parses and performs the DROP subcommand of GET and SAVE.
+   Returns true if successful, false on failure.*/
+static bool
+drop_variables (struct dictionary *dict)
 {
-  int i;
+  struct variable **v;
+  size_t nv;
 
-  printf (_("\nVariables in dictionary:\n"));
-  for (i = 0; i < dict->nvar; i++)
-    printf ("%s, ", dict->var[i]->name);
-  printf ("\n");
-}
-#endif
-\f
-/* Clears internal state related to GET input procedure. */
-static void
-get_source_destroy_source (void)
-{
-  /* 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);
+  lex_match ('=');
+  if (!parse_variables (dict, &v, &nv, PV_NONE))
+    return false;
+  dict_delete_vars (dict, v, nv);
+  free (v);
+
+  if (dict_get_var_cnt (dict) == 0)
+    {
+      msg (SE, _("Cannot DROP all variables from dictionary."));
+      return false;
+    }
+  return true;
 }
 
-/* Reads all the cases from the data file and passes them to
-   write_case(). */
-static void
-get_source_read (void)
+/* Parses and performs the KEEP subcommand of GET and SAVE.
+   Returns true if successful, false on failure.*/
+static bool
+keep_variables (struct dictionary *dict)
 {
-  while (sfm_read_case (get_file, temp_case->data, &default_dict)
-        && write_case ())
-    ;
-  get_source_destroy_source ();
-}
+  struct variable **v;
+  size_t nv;
+  size_t i;
 
-struct case_stream get_source =
-  {
-    NULL,
-    get_source_read,
-    NULL,
-    NULL,
-    get_source_destroy_source,
-    NULL,
-    "GET",
-  };
+  lex_match ('=');
+  if (!parse_variables (dict, &v, &nv, PV_NONE))
+    return false;
+
+  /* Move the specified variables to the beginning. */
+  dict_reorder_vars (dict, v, nv);
+          
+  /* Delete the remaining variables. */
+  v = xnrealloc (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);
 
+  return true;
+}
 \f
 /* MATCH FILES. */
 
@@ -594,292 +737,341 @@ enum
 /* One of the files on MATCH FILES. */
 struct mtf_file
   {
-    struct mtf_file *next, *prev;
-                               /* Next, previous in the list of files. */
+    struct mtf_file *next, *prev; /* Next, previous in the list of files. */
     struct mtf_file *next_min; /* Next in the chain of minimums. */
     
     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 any_reader *reader;  /* 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. */
+
+    /* IN subcommand. */
+    char *in_name;              /* Variable name. */
+    struct variable *in_var;    /* Variable (in master dictionary). */
+
+    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. */
+    
+    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;
+    /* Names of FIRST, LAST variables. */
+    char first[LONG_NAME_LEN + 1], last[LONG_NAME_LEN + 1];
+    
+    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;
+  bool used_active_file = false;
+  bool saw_table = false;
+  bool saw_in = false;
   
-  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_cnt = 0;
+  mtf.first[0] = '\0';
+  mtf.last[0] = '\0';
+  mtf.dict = dict_create ();
+  mtf.sink = NULL;
+  case_nullify (&mtf.mtf_case);
+  mtf.seq_num = 0;
+  mtf.seq_nums = NULL;
+  dict_set_case_limit (mtf.dict, dict_get_case_limit (default_dict));
+
+  lex_match ('/');
+  while (token == T_ID
+         && (lex_id_match ("FILE", tokid) || lex_id_match ("TABLE", tokid)))
+    {
+      struct mtf_file *file = xmalloc (sizeof *file);
+
+      if (lex_match_id ("FILE"))
+        file->type = MTF_FILE;
+      else if (lex_match_id ("TABLE"))
+        {
+          file->type = MTF_TABLE;
+          saw_table = true;
+        }
+      else
+        assert (0);
+      lex_match ('=');
+
+      file->by = NULL;
+      file->handle = NULL;
+      file->reader = NULL;
+      file->dict = NULL;
+      file->in_name = NULL;
+      file->in_var = NULL;
+      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;
+          if (file->type == MTF_TABLE && first_table == NULL)
+            first_table = file;
+        }
+      else 
+        {
+          assert (file->type == MTF_FILE);
+          file->next = first_table;
+          file->prev = first_table->prev;
+          if (first_table->prev)
+            first_table->prev->next = file;
+          else
+            mtf.head = file;
+          first_table->prev = file;
+        }
+
+      if (lex_match ('*'))
+        {
+          file->handle = NULL;
+          file->reader = NULL;
+              
+          if (used_active_file)
+            {
+              msg (SE, _("The active file may not be specified more "
+                         "than once."));
+              goto error;
+            }
+          used_active_file = true;
+
+          assert (pgm_state != STATE_INPUT);
+          if (pgm_state == STATE_INIT)
+            {
+              msg (SE, _("Cannot specify the active file since no active "
+                         "file has been defined."));
+              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 (FH_REF_FILE | FH_REF_SCRATCH);
+          if (file->handle == NULL)
+            goto error;
+
+          file->reader = any_reader_open (file->handle, &file->dict);
+          if (file->reader == NULL)
+            goto error;
+
+          case_create (&file->input, dict_get_next_value_idx (file->dict));
+        }
+
+      while (lex_match ('/'))
+        if (lex_match_id ("RENAME")) 
+          {
+            if (!rename_variables (file->dict))
+              goto error; 
+          }
+        else if (lex_match_id ("IN"))
+          {
+            lex_match ('=');
+            if (token != T_ID)
+              {
+                lex_error (NULL);
+                goto error;
+              }
+
+            if (file->in_name != NULL)
+              {
+                msg (SE, _("Multiple IN subcommands for a single FILE or "
+                           "TABLE."));
+                goto error;
+              }
+            file->in_name = xstrdup (tokid);
+            lex_get ();
+            saw_in = true;
+          }
+
+      mtf_merge_dictionary (mtf.dict, file);
+    }
   
-  do
+  while (token != '.')
     {
-      lex_match ('/');
-
       if (lex_match (T_BY))
        {
-         if (seen & 1)
+          struct variable **by;
+          
+         if (mtf.by_cnt)
            {
-             msg (SE, _("The BY subcommand may be given once at most."));
-             goto lossage;
+             msg (SE, _("BY may appear at most once."));
+             goto error;
            }
-         seen |= 1;
              
          lex_match ('=');
-         if (!parse_variables (mtf_master, &mtf_by, &mtf_n_by,
+         if (!parse_variables (mtf.dict, &by, &mtf.by_cnt,
                                PV_NO_DUPLICATE | PV_NO_SCRATCH))
-           goto lossage;
-       }
-      else if (token != T_ID)
-       {
-         lex_error (NULL);
-         goto lossage;
-       }
-      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"))
-           {
-             file->type = MTF_TABLE;
-             seen |= 4;
-           }
-         else
-           assert (0);
+           goto error;
 
-         /* 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;
-             if (file->type == MTF_TABLE && first_table == NULL)
-               first_table = file;
-           }
-         else 
-           {
-             assert (file->type == MTF_FILE);
-             file->next = first_table;
-             file->prev = first_table->prev;
-             if (first_table->prev)
-               first_table->prev->next = file;
-             else
-               mtf_head = file;
-             first_table->prev = file;
-           }
+          for (iter = mtf.head; iter != NULL; iter = iter->next)
+            {
+              size_t i;
          
-         lex_match ('=');
-         
-         if (lex_match ('*'))
-           {
-             file->handle = NULL;
-
-             if (seen & 2)
-               {
-                 msg (SE, _("The active file may not be specified more "
-                            "than once."));
-                 goto lossage;
-               }
-             seen |= 2;
-
-             assert (pgm_state != STATE_INPUT);
-             if (pgm_state == STATE_INIT)
-               {
-                 msg (SE, _("Cannot specify the active file since no active "
-                            "file has been defined."));
-                 goto lossage;
-               }
-           }
-         else
-           {
-             file->handle = fh_parse_file_handle ();
-             if (!file->handle)
-               goto lossage;
-           }
-
-         if (file->handle)
-           {
-             file->dict = sfm_read_dictionary (file->handle, NULL);
-             if (!file->dict)
-               goto lossage;
-           }
-         else
-           file->dict = &default_dict;
-         if (!mtf_merge_dictionary (file))
-           goto lossage;
+              iter->by = xnmalloc (mtf.by_cnt, sizeof *iter->by);
+
+              for (i = 0; i < mtf.by_cnt; i++)
+                {
+                  iter->by[i] = dict_lookup_var (iter->dict, by[i]->name);
+                  if (iter->by[i] == NULL)
+                    {
+                      msg (SE, _("File %s lacks BY variable %s."),
+                           iter->handle ? fh_get_name (iter->handle) : "*",
+                           by[i]->name);
+                      free (by);
+                      goto error;
+                    }
+                }
+            }
+          free (by);
        }
-      else if (lex_id_match ("IN", tokid)
-              || lex_id_match ("FIRST", tokid)
-              || lex_id_match ("LAST", tokid))
-       {
-         const char *sbc;
-         char *name;
-         
-         if (mtf_tail == NULL)
-           {
-             msg (SE, _("IN, FIRST, and LAST subcommands may not occur "
-                        "before the first FILE or TABLE."));
-             goto lossage;
-           }
-
-         if (lex_match_id ("IN"))
-           {
-             name = mtf_tail->in;
-             sbc = "IN";
-           }
-         else if (lex_match_id ("FIRST"))
-           {
-             name = mtf_tail->first;
-             sbc = "FIRST";
-           }
-         else if (lex_match_id ("LAST"))
-           {
-             name = mtf_tail->last;
-             sbc = "LAST";
-           }
-         else
-           assert (0);
-
+      else if (lex_match_id ("FIRST")) 
+        {
+          if (mtf.first[0] != '\0')
+            {
+              msg (SE, _("FIRST may appear at most once."));
+              goto error;
+            }
+             
          lex_match ('=');
-         if (token != T_ID)
-           {
-             lex_error (NULL);
-             goto lossage;
-           }
-
-         if (*name)
-           {
-             msg (SE, _("Multiple %s subcommands for a single FILE or "
-                        "TABLE."),
-                  sbc);
-             goto lossage;
-           }
-         strcpy (name, tokid);
-         lex_get ();
-
-         if (!create_variable (mtf_master, name, NUMERIC, 0))
-           {
-             msg (SE, _("Duplicate variable name %s while creating %s "
-                        "variable."),
-                  name, sbc);
-             goto lossage;
-           }
-       }
-      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)
-           {
-             msg (SE, _("RENAME, KEEP, and DROP subcommands may not occur "
-                        "before the first FILE or TABLE."));
-             goto lossage;
-           }
-
-         if (!trim_dictionary (mtf_tail->dict, &options))
-           goto lossage;
-       }
+          if (!lex_force_id ())
+            goto error;
+          strcpy (mtf.first, tokid);
+          lex_get ();
+        }
+      else if (lex_match_id ("LAST")) 
+        {
+          if (mtf.last[0] != '\0')
+            {
+              msg (SE, _("LAST may appear at most once."));
+              goto error;
+            }
+             
+         lex_match ('=');
+          if (!lex_force_id ())
+            goto error;
+          strcpy (mtf.last, tokid);
+          lex_get ();
+        }
       else if (lex_match_id ("MAP"))
        {
          /* FIXME. */
        }
+      else if (lex_match_id ("DROP")) 
+        {
+          if (!drop_variables (mtf.dict))
+            goto error;
+        }
+      else if (lex_match_id ("KEEP")) 
+        {
+          if (!keep_variables (mtf.dict))
+            goto error;
+        }
       else
        {
          lex_error (NULL);
-         goto lossage;
+         goto error;
        }
+
+      if (!lex_match ('/') && token != '.') 
+        {
+          lex_end_of_command ();
+          goto error;
+        }
     }
-  while (token != '.');
 
-  if (seen & 4)
+  if (mtf.by_cnt == 0)
     {
-      if (!(seen & 1))
-       {
-         msg (SE, _("The BY subcommand is required when a TABLE subcommand "
-                    "is given."));
-         goto lossage;
-       }
+      if (saw_table)
+        {
+          msg (SE, _("BY is required when TABLE is specified."));
+          goto error;
+        }
+      if (saw_in)
+        {
+          msg (SE, _("BY is required when IN is specified."));
+          goto error;
+        }
     }
 
-  if (seen & 1)
+  /* Set up mapping from each file's variables to master
+     variables. */
+  for (iter = mtf.head; iter != NULL; iter = iter->next)
     {
-      struct mtf_file *iter;
-
-      for (iter = mtf_head; iter; iter = iter->next)
-       {
-         int i;
-         
-         iter->by = xmalloc (sizeof *iter->by * mtf_n_by);
+      struct dictionary *d = iter->dict;
+      int i;
 
-         for (i = 0; i < mtf_n_by; i++)
-           {
-             iter->by[i] = find_dict_variable (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;
-               }
-           }
-       }
+      for (i = 0; i < dict_get_var_cnt (d); i++)
+        {
+          struct variable *v = dict_get_var (d, i);
+          struct variable *mv = dict_lookup_var (mtf.dict, v->name);
+          if (mv != NULL)
+            set_master (v, mv);
+        }
     }
 
-#if DEBUGGING
-  {
-    /* From sfm-read.c. */
-    extern void dump_dictionary (struct dictionary *);
-
-    dump_dictionary (mtf_master);
-  }
-#endif
-
+  /* Add IN variables to master dictionary. */
+  for (iter = mtf.head; iter != NULL; iter = iter->next) 
+    if (iter->in_name != NULL)
+      {
+        iter->in_var = dict_create_var (mtf.dict, iter->in_name, 0);
+        if (iter->in_var == NULL)
+          {
+            msg (SE, _("IN variable name %s duplicates an "
+                       "existing variable name."),
+                 iter->in_var->name);
+            goto error;
+          }
+        iter->in_var->print = iter->in_var->write
+          = make_output_format (FMT_F, 1, 0);
+      }
+    
   /* MATCH FILES performs an n-way merge on all its input files.
      Abstract algorithm:
 
@@ -887,72 +1079,82 @@ cmd_match_files (void)
 
      2. If no FILEs are left, stop.  Otherwise, proceed to step 3.
 
-     3. Find the FILE input record with minimum BY values.  Store all
-     the values from this input record into the output record.
-
-     4. Find all the FILE input records with BY values identical to
-     the minimums.  Store all the values from these input records into
+     3. Find the FILE input record(s) that have minimum BY
+     values.  Store all the values from these input records into
      the output record.
 
-     5. For every TABLE, read another record as long as the BY values
+     4. For every TABLE, read another record as long as the BY values
      on the TABLE's input record are less than the FILEs' BY values.
      If an exact match is found, store all the values from the TABLE
      input record into the output record.
 
-     6. Write the output record.
+     5. Write the output record.
 
-     7. Read another record from each input file FILE and TABLE that
+     6. Read another record from each input file FILE and TABLE that
      we stored values from above.  If we come to the end of one of the
      input files, remove it from the list of input files.
 
-     8. Repeat from step 2.
+     7. Repeat from step 2.
 
-     Unfortunately, this algorithm can't be directly implemented
-     because there's no function to read a record from the active
-     file; instead, it has to be done using callbacks.
+     Unfortunately, this algorithm can't be implemented in a
+     straightforward way because there's no function to read a
+     record from the active file.  Instead, it has to be written
+     as a state machine.
 
-     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))
+  if (!used_active_file)
     discard_variables ();
 
-  temporary = 2;
-  temp_dict = mtf_master;
-  temp_trns = n_trns;
+  dict_compact_values (mtf.dict);
+  mtf.sink = create_case_sink (&storage_sink_class, mtf.dict, NULL);
+  if (mtf.sink->class->open != NULL)
+    mtf.sink->class->open (mtf.sink);
+
+  mtf.seq_nums = xcalloc (dict_get_var_cnt (mtf.dict), sizeof *mtf.seq_nums);
+  case_create (&mtf.mtf_case, dict_get_next_value_idx (mtf.dict));
+
+  mtf_read_nonactive_records (&mtf);
+  if (used_active_file)
+    procedure (mtf_processing, &mtf);
+  mtf_processing_finish (&mtf);
 
-  process_active_file (mtf_read_nonactive_records, mtf_processing,
-                      mtf_processing_finish);
-  mtf_master = NULL;
+  free_case_source (vfm_source);
+  vfm_source = 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. */
+/* Repeats 2...7 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;
 }
 
@@ -982,245 +1184,190 @@ 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);
+  any_reader_close (file->reader);
+  if (file->dict != default_dict)
+    dict_destroy (file->dict);
+  case_destroy (&file->input);
+  free (file->in_name);
   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);
+  if (mtf->dict)
+    dict_destroy (mtf->dict);
+  case_destroy (&mtf->mtf_case);
+  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;
+  int i;
 
   if (f->prev)
     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++)
-      {
-       struct variable *v = f->dict->var[i];
+  if (f->in_var != NULL)
+    case_data_rw (&mtf->mtf_case, f->in_var->fv)->f = 0.;
+  for (i = 0; i < dict_get_var_cnt (f->dict); i++)
+    {
+      struct variable *v = dict_get_var (f->dict, i);
+      struct variable *mv = get_master (v);
+      if (mv != NULL) 
+        {
+          union value *out = case_data_rw (&mtf->mtf_case, mv->fv);
          
-       if (v->type == NUMERIC)
-         compaction_case->data[v->p.mtf.master->fv].f = SYSMIS;
-       else
-         memset (compaction_case->data[v->p.mtf.master->fv].s, ' ',
-                 v->width);
-      }
-  }
-  
+          if (v->type == NUMERIC)
+            out->f = SYSMIS;
+          else
+            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_)
 {
-  struct mtf_file *iter;
+  struct mtf_proc *mtf = mtf_;
+  struct mtf_file *iter, *next;
 
-  for (iter = mtf_head; iter; )
+  for (iter = mtf->head; iter != NULL; iter = next)
     {
-      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);
-         else
-           iter = iter->next;
-       }
-      else
-       {
-         iter->input = temp_case->data;
-         iter = iter->next;
-       }
+      next = iter->next;
+      if (iter->handle && !any_reader_read (iter->reader, &iter->input))
+        mtf_delete_file_in_place (mtf, &iter);
     }
 }
 
 /* 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)
 {
-  int i;
-  
-  for (i = 0; i < mtf_n_by; 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;
-
-         if (af < bf)
-           return -1;
-         else if (af > bf)
-           return 1;
-       }
-      else 
-       {
-         int result;
-         
-         assert (a->by[i]->type == ALPHA);
-         result = memcmp (a->input[a->by[i]->fv].s,
-                          b->input[b->by[i]->fv].s,
-                          a->by[i]->width);
-         if (result < 0)
-           return -1;
-         else if (result > 0)
-           return 1;
-       }
-    }
-  return 0;
+  struct ccase *ca = case_is_null (&a->input) ? c : &a->input;
+  struct ccase *cb = case_is_null (&b->input) ? c : &b->input;
+  assert ((a == NULL) + (b == NULL) + (c == NULL) <= 1);
+  return case_compare_2dict (ca, cb, a->by, b->by, mtf->by_cnt);
 }
 
-/* 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_)
 {
-  /* 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;
+  struct mtf_proc *mtf = mtf_;
 
-  /* Iterator. */
-  struct mtf_file *iter;
+  /* Do we need another record from the active file? */
+  bool read_active_file;
 
-  for (;;)
+  assert (mtf->head != NULL);
+  if (mtf->head->type == MTF_TABLE)
+    return 1;
+  
+  do
     {
-      /* If the active file doesn't have the minimum BY values, don't
-        return because that would cause a record to be skipped. */
-      int advance = 1;
+      struct mtf_file *min_head, *min_tail; /* Files with minimum BY values. */
+      struct mtf_file *max_head, *max_tail; /* Files with non-minimum BYs. */
+      struct mtf_file *iter, *next;
 
-      if (mtf_head->type == MTF_TABLE)
-       return 0;
+      read_active_file = false;
       
-      /* 3. Find the FILE input record with minimum BY values.  Store
-        all the values from this input record into the output record.
-
-        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;
+      /* 3. Find the FILE input record(s) that have minimum BY
+         values.  Store all the values from these input records into
+         the output record. */
+      min_head = min_tail = mtf->head;
       max_head = max_tail = NULL;
-      for (iter = mtf_head->next; iter && iter->type == MTF_FILE;
-          iter = iter->next)
-       switch (mtf_compare_BY_values (min_head, iter))
-         {
-         case -1:
-           if (max_head)
-             max_tail = max_tail->next_min = iter;
-           else
-             max_head = max_tail = iter;
-           break;
-
-         case 0:
+      for (iter = mtf->head->next; iter && iter->type == MTF_FILE;
+          iter = iter->next) 
+        {
+          int cmp = mtf_compare_BY_values (mtf, min_head, iter, c);
+          if (cmp < 0) 
+            {
+              if (max_head)
+                max_tail = max_tail->next_min = iter;
+              else
+                max_head = max_tail = iter;
+            }
+          else if (cmp == 0) 
            min_tail = min_tail->next_min = iter;
-           break;
-
-         case 1:
-           if (max_head)
-             {
-               max_tail->next_min = min_head;
-               max_tail = min_tail;
-             }
-           else
-             {
-               max_head = min_head;
-               max_tail = min_tail;
-             }
-           min_head = min_tail = iter;
-           break;
-
-         default:
-           assert (0);
-         }
-
-      /* 5. For every TABLE, read another record as long as the BY
+          else /* cmp > 0 */
+            {
+              if (max_head)
+                {
+                  max_tail->next_min = min_head;
+                  max_tail = min_tail;
+                }
+              else
+                {
+                  max_head = min_head;
+                  max_tail = min_tail;
+                }
+              min_head = min_tail = iter;
+            }
+        }
+      
+      /* 4. For every TABLE, read another record as long as the BY
         values on the TABLE's input record are less than the FILEs'
         BY values.  If an exact match is found, store all the values
         from the TABLE input record into the output record. */
-      while (iter)
+      for (; iter != NULL; iter = next)
        {
-         struct mtf_file *next = iter->next;
-         
          assert (iter->type == MTF_TABLE);
       
-         if (iter->handle == NULL)
-           advance = 0;
-
-       again:
-         switch (mtf_compare_BY_values (min_head, iter))
-           {
-           case -1:
-             if (max_head)
-               max_tail = max_tail->next_min = iter;
-             else
-               max_head = max_tail = iter;
-             break;
-
-           case 0:
-             min_tail = min_tail->next_min = iter;
-             break;
-
-           case 1:
-             if (iter->handle == NULL)
-               return 1;
-             if (sfm_read_case (iter->handle, iter->input, iter->dict))
-               goto again;
-             mtf_delete_file_in_place (&iter);
-             break;
-
-           default:
-             assert (0);
-           }
-
-         iter = next;
+         next = iter->next;
+          for (;;) 
+            {
+              int cmp = mtf_compare_BY_values (mtf, min_head, iter, c);
+              if (cmp < 0) 
+                {
+                  if (max_head)
+                    max_tail = max_tail->next_min = iter;
+                  else
+                    max_head = max_tail = iter;
+                }
+              else if (cmp == 0)
+                min_tail = min_tail->next_min = iter;
+              else /* cmp > 0 */
+                {
+                  if (iter->handle == NULL)
+                    return 1;
+                  if (any_reader_read (iter->reader, &iter->input))
+                    continue;
+                  mtf_delete_file_in_place (mtf, &iter);
+                }
+              break;
+            }
        }
 
       /* Next sequence number. */
-      mtf_seq_no++;
-  
+      mtf->seq_num++;
+
       /* Store data to all the records we are using. */
       if (min_tail)
        min_tail->next_min = NULL;
@@ -1228,369 +1375,286 @@ 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 variable *mv = get_master (v);
          
-             if (v->p.mtf.master->foo == mtf_seq_no)
-               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
-             if (v->type == NUMERIC)
-               compaction_case->data[v->p.mtf.master->fv].f
-                 = iter->input[v->fv].f;
-             else
-               {
-                 assert (v->type == ALPHA);
-                 memcpy (compaction_case->data[v->p.mtf.master->fv].s,
-                         iter->input[v->fv].s, v->width);
-               }
-           }
+             if (mv != NULL && mtf->seq_nums[mv->index] != mtf->seq_num) 
+                {
+                  struct ccase *record
+                    = case_is_null (&iter->input) ? c : &iter->input;
+                  union value *out = case_data_rw (&mtf->mtf_case, mv->fv);
+
+                  mtf->seq_nums[mv->index] = mtf->seq_num;
+                  if (v->type == NUMERIC)
+                    out->f = case_num (record, v->fv);
+                  else
+                    memcpy (out->s, case_str (record, v->fv), v->width);
+                } 
+            }
+          if (iter->in_var != NULL)
+            case_data_rw (&mtf->mtf_case, iter->in_var->fv)->f = 1.;
+
+          if (iter->type == MTF_FILE && iter->handle == NULL)
+            read_active_file = true;
        }
 
-      /* Store missing values to all the records we're not using. */
+      /* Store missing values to all the records we're not
+         using. */
       if (max_tail)
        max_tail->next_min = NULL;
       for (iter = max_head; iter; iter = iter->next_min)
        {
          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];
-         
-             if (v->p.mtf.master->foo == mtf_seq_no)
-               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
-             if (v->type == NUMERIC)
-               compaction_case->data[v->p.mtf.master->fv].f = SYSMIS;
-             else
-                memset (compaction_case->data[v->p.mtf.master->fv].s, ' ',
-                        v->width);
-           }
-
-         if (iter->handle == NULL)
-           advance = 0;
+             struct variable *v = dict_get_var (iter->dict, i);
+              struct variable *mv = get_master (v);
+
+             if (mv != NULL && mtf->seq_nums[mv->index] != mtf->seq_num) 
+                {
+                  union value *out = case_data_rw (&mtf->mtf_case, mv->fv);
+                  mtf->seq_nums[mv->index] = mtf->seq_num;
+
+                  if (v->type == NUMERIC)
+                    out->f = SYSMIS;
+                  else
+                    memset (out->s, ' ', v->width);
+                }
+            }
+          if (iter->in_var != NULL)
+            case_data_rw (&mtf->mtf_case, iter->in_var->fv)->f = 0.;
        }
 
-      /* 6. Write the output record. */
-      process_active_file_output_case ();
+      /* 5. Write the output record. */
+      mtf->sink->class->write (mtf->sink, &mtf->mtf_case);
 
-      /* 7. Read another record from each input file FILE and TABLE
+      /* 6. Read another record from each input file FILE and TABLE
         that we stored values from above.  If we come to the end of
         one of the input files, remove it from the list of input
         files. */
-      for (iter = min_head; iter && iter->type == MTF_FILE; )
+      for (iter = min_head; iter && iter->type == MTF_FILE; iter = next)
        {
-         struct mtf_file *next = iter->next_min;
-         
-         if (iter->handle)
-           {
-             assert (iter->input != NULL);
-
-             if (!sfm_read_case (iter->handle, iter->input, iter->dict))
-               mtf_delete_file_in_place (&iter);
-           }
-
-         iter = next;
+         next = iter->next_min;
+         if (iter->reader != NULL
+              && !any_reader_read (iter->reader, &iter->input))
+            mtf_delete_file_in_place (mtf, &iter);
        }
-      
-      if (advance)
-       break;
     }
+  while (!read_active_file
+         && mtf->head != NULL && mtf->head->type == MTF_FILE);
 
-  return (mtf_head && mtf_head->type != MTF_TABLE);
+  return mtf->head != NULL && mtf->head->type == MTF_FILE;
 }
 
-/* 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;
+  int i;
 
-  if (d->documents)
+  if (dict_get_label (m) == NULL)
+    dict_set_label (m, dict_get_label (d));
+
+  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);
+        }
+    }
+  
+  for (i = 0; i < dict_get_var_cnt (d); i++)
+    {
+      struct variable *dv = dict_get_var (d, i);
+      struct variable *mv = dict_lookup_var (m, dv->name);
+
+      if (dict_class_from_id (dv->name) == DC_SCRATCH)
+        continue;
+
+      if (mv != NULL)
+        {
+          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_get_name (f->handle),
+                   var_type_description (dv), var_type_description (mv));
+              return 0;
+            }
+        
+          if (dv->width == mv->width)
+            {
+              if (val_labs_count (dv->val_labs)
+                  && !val_labs_count (mv->val_labs))
+                mv->val_labs = val_labs_copy (dv->val_labs);
+              if (!mv_is_empty (&dv->miss) && mv_is_empty (&mv->miss))
+                mv_copy (&mv->miss, &dv->miss);
+            }
+
+          if (dv->label && !mv->label)
+            mv->label = xstrdup (dv->label);
+        }
+      else
+        mv = dict_clone_var_assert (m, dv, dv->name);
     }
-      
-  {
-    int i;
-
-    d->nval = 0;
-    for (i = 0; i < d->nvar; i++)
-      {
-       struct variable *dv = d->var[i];
-       struct variable *mv = find_dict_variable (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 (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;
-         }
-       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),
-                var_type_description (dv), var_type_description (mv));
-           return 0;
-         }
-       dv->p.mtf.master = mv;
-      }
-  }
 
   return 1;
 }
-\f
-/* IMPORT command. */
 
-/* Parses the IMPORT command. */
-int
-cmd_import (void)
+/* Marks V's master variable as MASTER. */
+static void
+set_master (struct variable *v, struct variable *master) 
 {
-  struct file_handle *handle = NULL;
-  struct dictionary *dict;
-  int options = GTSV_OPT_NONE;
-  int type;
+  var_attach_aux (v, master, NULL);
+}
 
-  int i;
-  int nval;
+/* Returns the master variable corresponding to V,
+   as set with set_master(). */
+static struct variable *
+get_master (struct variable *v) 
+{
+  return v->aux;
+}
+\f
 
-  lex_match_id ("IMPORT");
+\f
+/* Case map.
 
-  for (;;)
-    {
-      lex_match ('/');
-      
-      if (lex_match_id ("FILE") || token == T_STRING)
-       {
-         lex_match ('=');
+   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.)
+   */
 
-         handle = fh_parse_file_handle ();
-         if (handle == NULL)
-           return CMD_FAILURE;
-       }
-      else if (lex_match_id ("TYPE"))
-       {
-         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. */
+  };
 
-         if (lex_match_id ("COMM"))
-           type = PFM_COMM;
-         else if (lex_match_id ("TAPE"))
-           type = PFM_TAPE;
-         else
-           {
-             lex_error (_("expecting COMM or TAPE"));
-             return CMD_FAILURE;
-           }
-       }
-      else break;
-    }
-  if (!lex_match ('/') && token != '.')
+/* 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.
+
+   Uses D's aux members, which must otherwise not 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++)
     {
-      lex_error (NULL);
-      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);
     }
+}
 
-  discard_variables ();
+/* Produces a case map from dictionary D, which must have been
+   previously prepared with start_case_map().
 
-  dict = pfm_read_dictionary (handle, NULL);
-  if (dict == NULL)
-    return CMD_FAILURE;
+   Does not retain any reference to D, and clears the aux members
+   set up by start_case_map().
 
-#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++)
+   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 = xnmalloc (map->value_cnt, sizeof *map->map);
+  for (i = 0; i < map->value_cnt; i++)
+    map->map[i] = -1;
+
+  identity_map = 1;
+  for (i = 0; i < var_cnt; i++) 
     {
-      struct variable *v = dict->var[i];
+      struct variable *v = dict_get_var (d, i);
+      int *src_fv = (int *) var_detach_aux (v);
+      size_t idx;
 
-      v->fv = nval;
-      nval += v->nv;
+      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);
     }
-  dict->nval = nval;
-  assert (nval);
 
-#if DEBUGGING
-  printf (_("IMPORT translation table from file to memory:\n"));
-  for (i = 0; i < dict->nvar; i++)
+  if (identity_map) 
     {
-      struct variable *v = dict->var[i];
-
-      printf (_("  %8s from %3d,%3d to %3d,%3d\n"), v->name,
-             v->get.fv, v->get.nv, v->fv, v->nv);
+      destroy_case_map (map);
+      return NULL;
     }
-#endif
-
-  restore_dictionary (dict);
 
-  vfm_source = &import_source;
-  get_file = handle;
+  while (map->value_cnt > 0 && map->map[map->value_cnt - 1] == -1)
+    map->value_cnt--;
 
-  return CMD_SUCCESS;
+  return map;
 }
 
-/* Reads all the cases from the data file and passes them to
-   write_case(). */
+/* Maps from SRC to DST, applying case map MAP. */
 static void
-import_source_read (void)
-{
-  while (pfm_read_case (get_file, temp_case->data, &default_dict)
-        && write_case ())
-    ;
-  get_source_destroy_source ();
-}
-
-struct case_stream import_source =
-  {
-    NULL,
-    import_source_read,
-    NULL,
-    NULL,
-    get_source_destroy_source,
-    NULL,
-    "IMPORT",
-  };
-\f
-static int export_write_case_func (struct ccase *c);
-     
-/* Parses the EXPORT command.  */
-/* FIXME: same as cmd_save_internal(). */
-int
-cmd_export (void)
+map_case (const struct case_map *map,
+          const struct ccase *src, struct ccase *dst) 
 {
-  struct file_handle *handle;
-  struct dictionary *dict;
-  int options = GTSV_OPT_SAVE;
-
-  struct save_trns *t;
-
-  int i;
-
-  lex_match_id ("EXPORT");
-
-  lex_match ('/');
-  if (lex_match_id ("OUTFILE"))
-    lex_match ('=');
-
-  handle = fh_parse_file_handle ();
-  if (handle == NULL)
-    return CMD_FAILURE;
-
-  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;
-    }
+  size_t dst_idx;
 
-#if DEBUGGING
-  dump_dict_variables (dict);
-#endif
+  assert (map != NULL);
+  assert (src != NULL);
+  assert (dst != NULL);
+  assert (src != dst);
 
-  /* Write dictionary. */
-  if (!pfm_write_dictionary (handle, dict))
+  for (dst_idx = 0; dst_idx < map->value_cnt; dst_idx++)
     {
-      free_dictionary (dict);
-      fh_close_handle (handle);
-      return CMD_FAILURE;
+      int src_idx = map->map[dst_idx];
+      if (src_idx != -1)
+        *case_data_rw (dst, dst_idx) = *case_data (src, src_idx);
     }
-
-  /* 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);
-
-  procedure (NULL, export_write_case_func, NULL);
-  save_trns_free ((struct trns_header *) t);
-
-  return CMD_SUCCESS;
 }
 
-static int
-export_write_case_func (struct ccase *c)
+/* Destroys case map MAP. */
+static void
+destroy_case_map (struct case_map *map) 
 {
-  union value *p = (union value *) trns->case_buf;
-  int i;
-
-  for (i = 0; i < trns->nvar; i++)
+  if (map != NULL) 
     {
-      struct variable *v = default_dict.var[trns->var[i]];
-
-      if (v->type == NUMERIC)
-       *p++ = c->data[v->fv];
-      else
-       (*p++).c = c->data[v->fv].s;
+      free (map->map);
+      free (map);
     }
-
-  printf (".");
-  fflush (stdout);
-  
-  pfm_write_case (trns->f, (union value *) trns->case_buf);
-  return 1;
 }