Added preamble
[pspp-builds.git] / src / get.c
index 057b50725b792ca5f92b50c0a600af29340eb953..e47850c364919c2d4d01481112a443ab1f8b9da2 100644 (file)
--- a/src/get.c
+++ b/src/get.c
@@ -14,8 +14,8 @@
 
    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 "error.h"
@@ -40,6 +40,9 @@
 #include "vfm.h"
 #include "vfmP.h"
 
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
 #include "debug-print.h"
 
 /* Rearranging and reducing a dictionary. */
@@ -54,12 +57,10 @@ enum operation
   {
     OP_READ,    /* GET or IMPORT. */
     OP_SAVE,    /* SAVE or XSAVE. */
-    OP_EXPORT,  /* EXPORT. */
-    OP_MATCH    /* MATCH FILES. */
+    OP_EXPORT   /* EXPORT. */
   };
 
-static int trim_dictionary (struct dictionary *,
-                            enum operation, int *compress);
+static bool parse_dict_trim (struct dictionary *);
 \f
 /* GET input program. */
 struct get_pgm 
@@ -99,8 +100,14 @@ cmd_get (void)
   case_create (&pgm->bounce, dict_get_next_value_idx (dict));
 
   start_case_map (dict);
-  if (!trim_dictionary (dict, OP_READ, NULL))
-    goto error;
+  while (lex_match ('/'))
+    if (!parse_dict_trim (dict))
+      goto error;
+
+  if (!lex_end_of_command ())
+    return false;
+
+  dict_compact_values (dict);
   pgm->map = finish_case_map (dict);
 
   dict_destroy (default_dict);
@@ -112,7 +119,7 @@ cmd_get (void)
 
  error:
   get_pgm_free (pgm);
-  if (dict != NULL)
+  if (dict != NULL) 
     dict_destroy (dict);
   return CMD_FAILURE;
 }
@@ -173,257 +180,391 @@ const struct case_source_class get_source_class =
     get_source_destroy,
   };
 \f
-/* XSAVE transformation and SAVE procedure. */
-struct save_trns
+/* Type of output file. */
+enum writer_type
   {
-    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. */
+    SYSFILE_WRITER,     /* System file. */
+    PORFILE_WRITER      /* Portable file. */
+  };
+
+/* Type of a command. */
+enum command_type 
+  {
+    XFORM_CMD,          /* Transformation. */
+    PROC_CMD            /* Procedure. */
   };
 
-static int save_write_case_func (struct ccase *, void *);
-static trns_proc_func save_trns_proc;
-static trns_free_func save_trns_free;
+/* Portable or system file writer plus a case map. */
+struct any_writer
+  {
+    enum writer_type writer_type;
+    void *writer;
+    struct case_map *map;       /* Map to output file dictionary
+                                   (null pointer for identity mapping). */
+    struct ccase bounce;        /* Bounce buffer for mapping (if needed). */
+  };
 
-/* Parses the SAVE or XSAVE command
-   and returns the parsed transformation. */
-static struct save_trns *
-cmd_save_internal (void)
+/* Destroys AW. */
+static void
+any_writer_destroy (struct any_writer *aw)
 {
-  struct file_handle *fh;
-  struct dictionary *dict = NULL;
-  struct save_trns *t = NULL;
-  int compress = get_scompression ();
-
-  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 ('/');
-  if (lex_match_id ("OUTFILE"))
-    lex_match ('=');
-  fh = fh_parse ();
-  if (fh == NULL)
-    goto error;
+  if (aw != NULL) 
+    {
+      switch (aw->writer_type) 
+        {
+        case PORFILE_WRITER:
+          pfm_close_writer (aw->writer);
+          break;
+        case SYSFILE_WRITER:
+          sfm_close_writer (aw->writer);
+          break;
+        }
+      destroy_case_map (aw->map);
+      case_destroy (&aw->bounce);
+      free (aw);
+    }
+}
+
+/* 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 any_writer *
+parse_write_command (enum writer_type writer_type,
+                     enum command_type command_type,
+                     bool *retain_unselected)
+{
+  /* Common data. */
+  struct file_handle *handle; /* Output file. */
+  struct dictionary *dict;    /* Dictionary for output file. */
+  struct any_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_type = writer_type;
+  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);
-  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));
+  dict_delete_scratch_vars (dict);
 
-  t->writer = sfm_open_writer (fh, dict, compress);
-  if (t->writer == NULL)
+  lex_match ('/');
+  for (;;)
+    {
+      if (lex_match_id ("OUTFILE"))
+       {
+          if (handle != NULL) 
+            {
+              lex_sbc_only_once ("OUTFILE");
+              goto error; 
+            }
+          
+         lex_match ('=');
+      
+         handle = fh_parse ();
+         if (handle == NULL)
+           goto error;
+       }
+      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"))
+       {
+         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;
 
-  dict_destroy (dict);
+  if (handle == NULL) 
+    {
+      lex_sbc_missing ("OUTFILE");
+      goto error;
+    }
 
-  return t;
+  dict_compact_values (dict);
+  aw->map = finish_case_map (dict);
+  if (aw->map != NULL)
+    case_create (&aw->bounce, dict_get_next_value_idx (dict));
+
+  switch (writer_type) 
+    {
+    case SYSFILE_WRITER:
+      aw->writer = sfm_open_writer (handle, dict, sysfile_opts);
+      break;
+    case PORFILE_WRITER:
+      aw->writer = pfm_open_writer (handle, dict, porfile_opts);
+      break;
+    }
+
+  dict_destroy (dict);
+  
+  return aw;
 
  error:
-  assert (t != NULL);
+  any_writer_destroy (aw);
   dict_destroy (dict);
-  save_trns_free (&t->h);
   return NULL;
 }
 
-/* Parses and performs the SAVE procedure. */
-int
-cmd_save (void)
+/* Writes case C to writer AW. */
+static void
+any_writer_write_case (struct any_writer *aw, struct ccase *c) 
 {
-  struct save_trns *t = cmd_save_internal ();
-  if (t != NULL) 
+  if (aw->map != NULL) 
     {
-      procedure (save_write_case_func, t);
-      save_trns_free (&t->h);
-      free(t);
-      return CMD_SUCCESS;
+      map_case (aw->map, c, &aw->bounce);
+      c = &aw->bounce; 
     }
-  else
+  
+  switch (aw->writer_type) 
+    {
+    case SYSFILE_WRITER:
+      sfm_write_case (aw->writer, c);
+      break;
+    case PORFILE_WRITER:
+      pfm_write_case (aw->writer, c);
+      break;
+    }
+}
+\f
+/* SAVE and EXPORT. */
+
+static int output_proc (struct ccase *, void *);
+
+/* Parses and performs the SAVE or EXPORT procedure. */
+static int
+parse_output_proc (enum writer_type writer_type)
+{
+  bool retain_unselected;
+  struct variable *saved_filter_variable;
+  struct any_writer *aw;
+
+  aw = parse_write_command (writer_type, PROC_CMD, &retain_unselected);
+  if (aw == NULL) 
     return CMD_FAILURE;
+
+  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);
+
+  any_writer_destroy (aw);
+  return CMD_SUCCESS;
+}
+
+/* Writes case C to file. */
+static int
+output_proc (struct ccase *c, void *aw_) 
+{
+  struct any_writer *aw = aw_;
+  any_writer_write_case (aw, c);
+  return 0;
 }
 
-/* Parses the XSAVE transformation command. */
 int
-cmd_xsave (void)
+cmd_save (void) 
 {
-  struct save_trns *t = cmd_save_internal ();
-  if (t != NULL) 
-    {
-      add_transformation (&t->h);
-      return CMD_SUCCESS; 
-    }
-  else
-    return CMD_FAILURE;
+  return parse_output_proc (SYSFILE_WRITER);
 }
 
-/* Writes the given C to the file specified by T. */
-static void
-do_write_case (struct save_trns *t, struct ccase *c) 
+int
+cmd_export (void) 
 {
-  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);
-    }
+  return parse_output_proc (PORFILE_WRITER);
 }
+\f
+/* XSAVE and XEXPORT. */
+
+/* Transformation. */
+struct output_trns 
+  {
+    struct any_writer *aw;      /* Writer. */
+  };
 
-/* Writes case C to the system file specified on SAVE. */
+static trns_proc_func output_trns_proc;
+static trns_free_func output_trns_free;
+
+/* Parses the XSAVE or XEXPORT transformation command. */
 static int
-save_write_case_func (struct ccase *c, void *aux UNUSED)
+parse_output_trns (enum writer_type writer_type) 
 {
-  do_write_case (aux, c);
-  return 1;
+  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;
+    }
+
+  add_transformation (output_trns_proc, output_trns_free, t);
+  return CMD_SUCCESS;
 }
 
-/* Writes case C to the system file specified on XSAVE. */
+/* Writes case C to the system file specified on XSAVE or XEXPORT. */
 static int
-save_trns_proc (struct trns_header *h, struct ccase *c, int case_num UNUSED)
+output_trns_proc (void *trns_, struct ccase *c, int case_num UNUSED)
 {
-  struct save_trns *t = (struct save_trns *) h;
-  do_write_case (t, c);
+  struct output_trns *t = trns_;
+  any_writer_write_case (t->aw, c);
   return -1;
 }
 
-/* Frees a SAVE transformation. */
+/* Frees an XSAVE or XEXPORT transformation. */
 static void
-save_trns_free (struct trns_header *t_)
+output_trns_free (void *trns_)
 {
-  struct save_trns *t = (struct save_trns *) t_;
+  struct output_trns *t = trns_;
 
-  if (t != NULL) 
+  if (t != NULL)
     {
-      sfm_close_writer (t->writer);
-      destroy_case_map (t->map);
-      case_destroy (&t->bounce);
+      any_writer_destroy (t->aw);
+      free (t);
     }
 }
 
-static int rename_variables (struct dictionary *dict);
+/* XSAVE command. */
+int
+cmd_xsave (void) 
+{
+  return parse_output_trns (SYSFILE_WRITER);
+}
+
+/* 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.
-
-   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, enum operation op, int *compress)
+   appropriately.  Returns true on success, false on failure. */
+static bool
+parse_dict_trim (struct dictionary *dict)
 {
-  assert ((compress != NULL) == (op == OP_SAVE));
-  if (get_scompression())
-    *compress = 1;
-
-  if (op == OP_SAVE || op == OP_EXPORT)
+  if (lex_match_id ("MAP")) 
     {
-      /* Delete all the scratch variables. */
-      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);
+      /* FIXME. */
+      return true;
     }
-  
-  while (op == OP_MATCH || lex_match ('/'))
-    {
-      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;
-
-         lex_match ('=');
-         if (!parse_variables (dict, &v, &nv, PV_NONE))
-           return 0;
-          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;
-
-          /* 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"))
-       {
-         if (!rename_variables (dict))
-           return 0;
-       }
-      else
-       {
-         lex_error (_("while expecting a valid subcommand"));
-         return 0;
-       }
-
-      if (dict_get_var_cnt (dict) == 0)
-       {
-         msg (SE, _("All variables deleted from system file dictionary."));
-         return 0;
-       }
-
-      if (op == OP_MATCH)
-        goto success;
-    }
-
-  if (token != '.')
+  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 end of command"));
-      return 0;
+      lex_error (_("expecting a valid subcommand"));
+      return false;
     }
-
- success:
-  if (op != OP_MATCH)
-    dict_compact_values (dict);
-  return 1;
 }
 
 /* Parses and performs the RENAME subcommand of GET and SAVE. */
-static int
+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;
@@ -439,8 +580,6 @@ rename_variables (struct dictionary *dict)
       if (!lex_force_match ('=')
          || !lex_force_id ())
        return 0;
-      if (!strncmp (tokid, v->name, 8))
-       return 1;
       if (dict_lookup_var (dict, tokid) != NULL)
        {
          msg (SE, _("Cannot rename %s as %s because there already exists "
@@ -462,7 +601,7 @@ 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 done;
@@ -476,9 +615,9 @@ rename_variables (struct dictionary *dict)
       if (nn != nv)
        {
          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);
+                     "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 (')'))
@@ -493,7 +632,7 @@ rename_variables (struct dictionary *dict)
     }
   success = 1;
 
-done:
+ done:
   for (i = 0; i < nn; i++)
     free (new_names[i]);
   free (new_names);
@@ -501,90 +640,53 @@ done:
 
   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. */
-  };
 
-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)
+/* Parses and performs the DROP subcommand of GET and SAVE.
+   Returns true if successful, false on failure.*/
+static bool
+drop_variables (struct dictionary *dict)
 {
-  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;
+  struct variable **v;
+  size_t nv;
 
- error:
-  dict_destroy (dict);
-  export_proc_free (proc);
-  free (proc);
-  return CMD_FAILURE;
-}
+  lex_match ('=');
+  if (!parse_variables (dict, &v, &nv, PV_NONE))
+    return false;
+  dict_delete_vars (dict, v, nv);
+  free (v);
 
-/* Writes case C to the EXPORT file. */
-static int
-export_write_case_func (struct ccase *c, void *aux) 
-{
-  struct export_proc *proc = aux;
-  if (proc->map == NULL)
-    pfm_write_case (proc->writer, c);
-  else 
+  if (dict_get_var_cnt (dict) == 0)
     {
-      map_case (proc->map, c, &proc->bounce);
-      pfm_write_case (proc->writer, &proc->bounce);
+      msg (SE, _("Cannot DROP all variables from dictionary."));
+      return false;
     }
-  return 1;
+  return true;
 }
 
-static void
-export_proc_free (struct export_proc *proc) 
+/* Parses and performs the KEEP subcommand of GET and SAVE.
+   Returns true if successful, false on failure.*/
+static bool
+keep_variables (struct dictionary *dict)
 {
-  if (proc != NULL) 
-    {
-      pfm_close_writer (proc->writer);
-      destroy_case_map (proc->map);
-      case_destroy (&proc->bounce);
-    }
+  struct variable **v;
+  size_t nv;
+  size_t i;
+
+  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. */
@@ -601,8 +703,7 @@ 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_*. */
@@ -610,8 +711,11 @@ struct mtf_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=. */
+
+    /* IN subcommand. */
+    char *in_name;              /* Variable name. */
+    struct variable *in_var;    /* Variable (in master dictionary). */
+
     struct ccase input;         /* Input record. */
   };
 
@@ -621,12 +725,14 @@ 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. */
 
+    /* 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. */
+    struct ccase mtf_case;      /* Case used for output. */
 
     unsigned seq_num;           /* Have we initialized this variable? */
     unsigned *seq_nums;         /* Sequence numbers for each var in dict. */
@@ -654,257 +760,284 @@ cmd_match_files (void)
   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;
   
   mtf.head = mtf.tail = NULL;
-  mtf.by = NULL;
   mtf.by_cnt = 0;
+  mtf.first[0] = '\0';
+  mtf.last[0] = '\0';
   mtf.dict = dict_create ();
   mtf.sink = NULL;
-  mtf.mtf_case = 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));
-  
-  do
-    {
-      lex_match ('/');
 
-      if (lex_match (T_BY))
-       {
-         if (seen & 1)
-           {
-             msg (SE, _("The BY subcommand may be given once at most."));
-             goto error;
-           }
-         seen |= 1;
-             
-         lex_match ('=');
-         if (!parse_variables (mtf.dict, &mtf.by, &mtf.by_cnt,
-                               PV_NO_DUPLICATE | PV_NO_SCRATCH))
-           goto error;
-       }
-      else if (token != T_ID)
-       {
-         lex_error (NULL);
-         goto error;
-       }
-      else if (lex_id_match ("FILE", tokid) || lex_id_match ("TABLE", tokid))
-       {
-         struct mtf_file *file = xmalloc (sizeof *file);
+  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;
-             seen |= 4;
-           }
-         else
-           assert (0);
+      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;
+        }
 
-         file->by = NULL;
+      if (lex_match ('*'))
+        {
           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;
-             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;
-           }
-         
-         lex_match ('=');
-         
-         if (lex_match ('*'))
-           {
-              file->handle = NULL;
-             file->reader = NULL;
               
-             if (seen & 2)
-               {
-                 msg (SE, _("The active file may not be specified more "
-                            "than once."));
-                 goto error;
-               }
-             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 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 ();
-             if (file->handle == NULL)
-               goto error;
-
-              file->reader = sfm_open_reader (file->handle, &file->dict, NULL);
-              if (file->reader == NULL)
-                goto error;
+          if (used_active_file)
+            {
+              msg (SE, _("The active file may not be specified more "
+                         "than once."));
+              goto error;
+            }
+          used_active_file = true;
 
-              case_create (&file->input, dict_get_next_value_idx (file->dict));
-           }
-       }
-      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 error;
-           }
+          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 (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 
+          if (temporary != 0)
             {
-              assert (0);
-              abort ();
+              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 (); 
             }
 
-         lex_match ('=');
-         if (token != T_ID)
-           {
-             lex_error (NULL);
-             goto error;
-           }
+          file->dict = default_dict;
+        }
+      else
+        {
+          file->handle = fh_parse ();
+          if (file->handle == NULL)
+            goto error;
 
-         if (*name)
-           {
-             msg (SE, _("Multiple %s subcommands for a single FILE or "
-                        "TABLE."),
-                  sbc);
-             goto error;
-           }
-         strcpy (name, tokid);
-         lex_get ();
+          file->reader = sfm_open_reader (file->handle, &file->dict, NULL);
+          if (file->reader == NULL)
+            goto error;
 
-         if (!dict_create_var (mtf.dict, name, 0))
-           {
-             msg (SE, _("Duplicate variable name %s while creating %s "
-                        "variable."),
-                  name, sbc);
-             goto error;
-           }
-       }
-      else if (lex_id_match ("RENAME", tokid)
-              || lex_id_match ("KEEP", tokid)
-              || lex_id_match ("DROP", tokid))
+          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);
+    }
+  
+  while (token != '.')
+    {
+      if (lex_match (T_BY))
        {
-         if (mtf.tail == NULL)
+          struct variable **by;
+          
+         if (mtf.by_cnt)
            {
-             msg (SE, _("RENAME, KEEP, and DROP subcommands may not occur "
-                        "before the first FILE or TABLE."));
+             msg (SE, _("BY may appear at most once."));
              goto error;
            }
-
-         if (!trim_dictionary (mtf.tail->dict, OP_MATCH, NULL))
+             
+         lex_match ('=');
+         if (!parse_variables (mtf.dict, &by, &mtf.by_cnt,
+                               PV_NO_DUPLICATE | PV_NO_SCRATCH))
            goto error;
+
+          for (iter = mtf.head; iter != NULL; iter = iter->next)
+            {
+              size_t i;
+         
+              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 ? handle_get_name (iter->handle) : "*",
+                           by[i]->name);
+                      free (by);
+                      goto error;
+                    }
+                }
+            }
+          free (by);
        }
+      else if (lex_match_id ("FIRST")) 
+        {
+          if (mtf.first[0] != '\0')
+            {
+              msg (SE, _("FIRST may appear at most once."));
+              goto error;
+            }
+             
+         lex_match ('=');
+          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 error;
        }
-    }
-  while (token != '.');
 
-  for (iter = mtf.head; iter != NULL; iter = iter->next) 
-    mtf_merge_dictionary (mtf.dict, iter);
+      if (!lex_match ('/') && token != '.') 
+        {
+          lex_end_of_command ();
+          goto error;
+        }
+    }
 
-  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 error;
-       }
+      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)
     {
-      for (iter = mtf.head; iter != NULL; iter = iter->next)
-       {
-         int i;
-         
-         iter->by = xmalloc (sizeof *iter->by * mtf.by_cnt);
+      struct dictionary *d = iter->dict;
+      int i;
 
-         for (i = 0; i < mtf.by_cnt; i++)
-           {
-             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 ? handle_get_name (iter->handle) : "*",
-                      mtf.by[i]->name);
-                 goto error;
-               }
-           }
-       }
+      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);
+        }
     }
 
+  /* 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:
 
@@ -912,49 +1045,50 @@ 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: 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 ();
 
+  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.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));
+  mtf_read_nonactive_records (&mtf);
+  if (used_active_file)
+    procedure (mtf_processing, &mtf);
+  mtf_processing_finish (&mtf);
 
-  mtf_read_nonactive_records (NULL);
-  if (seen & 2)
-    procedure (mtf_processing, NULL);
-  mtf_processing_finish (NULL);
+  free_case_source (vfm_source);
+  vfm_source = NULL;
 
   dict_destroy (default_dict);
   default_dict = mtf.dict;
@@ -965,12 +1099,12 @@ cmd_match_files (void)
   mtf_free (&mtf);
   return CMD_SUCCESS;
   
-error:
+ 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_)
 {
@@ -1021,6 +1155,7 @@ mtf_free_file (struct mtf_file *file)
   if (file->dict != default_dict)
     dict_destroy (file->dict);
   case_destroy (&file->input);
+  free (file->in_name);
   free (file);
 }
 
@@ -1033,13 +1168,12 @@ mtf_free (struct mtf_proc *mtf)
   for (iter = mtf->head; iter; iter = next)
     {
       next = iter->next;
-
       mtf_free_file (iter);
     }
   
-  free (mtf->by);
   if (mtf->dict)
     dict_destroy (mtf->dict);
+  case_destroy (&mtf->mtf_case);
   free (mtf->seq_nums);
 }
 
@@ -1049,6 +1183,7 @@ static void
 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;
@@ -1060,42 +1195,38 @@ mtf_delete_file_in_place (struct mtf_proc *mtf, struct mtf_file **file)
     mtf->tail = f->prev;
   *file = f->next;
 
-  {
-    int i;
-
-    for (i = 0; i < dict_get_var_cnt (f->dict); i++)
-      {
-       struct variable *v = dict_get_var (f->dict, i);
-        union value *out = case_data_rw (mtf->mtf_case, get_master (v)->fv);
+  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)
-          out->f = SYSMIS;
-       else
-         memset (out->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_ UNUSED)
+mtf_read_nonactive_records (void *mtf_)
 {
   struct mtf_proc *mtf = mtf_;
-  struct mtf_file *iter;
+  struct mtf_file *iter, *next;
 
-  for (iter = mtf->head; iter)
+  for (iter = mtf->head; iter != NULL; iter = next)
     {
-      if (iter->handle)
-       {
-         if (!sfm_read_case (iter->reader, &iter->input))
-           mtf_delete_file_in_place (mtf, &iter);
-         else
-           iter = iter->next;
-       }
-      else
-        iter = iter->next;
+      next = iter->next;
+      if (iter->handle && !sfm_read_case (iter->reader, &iter->input))
+        mtf_delete_file_in_place (mtf, &iter);
     }
 }
 
@@ -1106,148 +1237,103 @@ 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;
-
+  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);
-  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 = case_num (a_input, a->by[i]->fv);
-         double bf = case_num (b_input, b->by[i]->fv);
-
-         if (af < bf)
-           return -1;
-         else if (af > bf)
-           return 1;
-       }
-      else 
-       {
-         int result;
-         
-         assert (a->by[i]->type == ALPHA);
-         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;
-         else if (result > 0)
-           return 1;
-       }
-    }
-  return 0;
+  return case_compare_2dict (ca, cb, a->by, b->by, mtf->by_cnt);
 }
 
 /* Perform one iteration of steps 3...7 above. */
 static int
-mtf_processing (struct ccase *c, void *mtf_ UNUSED)
+mtf_processing (struct ccase *c, void *mtf_)
 {
   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 (;;)
+  /* Do we need another record from the active file? */
+  bool read_active_file;
+
+  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. */
+      /* 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 (mtf, min_head, iter, c))
-         {
-         case -1:
-           if (max_head)
-             max_tail = max_tail->next_min = iter;
-           else
-             max_head = max_tail = iter;
-           break;
-
-         case 0:
+          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 (mtf, min_head, iter, c))
-           {
-           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->reader, &iter->input))
-               goto again;
-             mtf_delete_file_in_place (mtf, &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 (sfm_read_case (iter->reader, &iter->input))
+                    continue;
+                  mtf_delete_file_in_place (mtf, &iter);
+                }
+              break;
+            }
        }
 
       /* Next sequence number. */
       mtf->seq_num++;
-  
+
       /* Store data to all the records we are using. */
       if (min_tail)
        min_tail->next_min = NULL;
@@ -1258,25 +1344,30 @@ mtf_processing (struct ccase *c, void *mtf_ UNUSED)
          for (i = 0; i < dict_get_var_cnt (iter->dict); i++)
            {
              struct variable *v = dict_get_var (iter->dict, i);
-              struct ccase *record;
-              union value *out;
+              struct variable *mv = get_master (v);
          
-             if (mtf->seq_nums[get_master (v)->index] == mtf->seq_num)
-               continue;
-              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)
-               out->f = case_num (record, v->fv);
-             else
-                memcpy (out->s, case_str (record, v->fv), 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)
@@ -1286,48 +1377,42 @@ mtf_processing (struct ccase *c, void *mtf_ UNUSED)
          for (i = 0; i < dict_get_var_cnt (iter->dict); i++)
            {
              struct variable *v = dict_get_var (iter->dict, i);
-              union value *out;
-         
-             if (mtf->seq_nums[get_master (v)->index] == mtf->seq_num)
-               continue;
-              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)
-                out->f = SYSMIS;
-             else
-                memset (out->s, ' ', v->width);
-           }
+              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 (iter->handle == NULL)
-           advance = 0;
+                  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. */
-      mtf->sink->class->write (mtf->sink, mtf->mtf_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->reader != NULL)
-           {
-             if (!sfm_read_case (iter->reader, &iter->input))
-               mtf_delete_file_in_place (mtf, &iter);
-           }
-
-         iter = next;
+         next = iter->next_min;
+         if (iter->reader != NULL
+              && !sfm_read_case (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 master dictionary M. */
@@ -1336,6 +1421,7 @@ mtf_merge_dictionary (struct dictionary *const m, struct mtf_file *f)
 {
   struct dictionary *d = f->dict;
   const char *d_docs, *m_docs;
+  int i;
 
   if (dict_get_label (m) == NULL)
     dict_set_label (m, dict_get_label (d));
@@ -1360,46 +1446,41 @@ mtf_merge_dictionary (struct dictionary *const m, struct mtf_file *f)
         }
     }
   
-  dict_compact_values (d);
+  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);
 
-  {
-    int i;
+      if (dict_class_from_id (dv->name) == DC_SCRATCH)
+        continue;
 
-    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);
-
-       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 = dict_clone_var (m, dv, dv->name);
-            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, handle_get_name (f->handle),
-                var_type_description (dv), var_type_description (mv));
-           return 0;
-         }
-        set_master (dv, mv);
-      }
-  }
+      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, handle_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);
+    }
 
   return 1;
 }
@@ -1416,7 +1497,6 @@ set_master (struct variable *v, struct variable *master)
 static struct variable *
 get_master (struct variable *v) 
 {
-  assert (v->aux != NULL);
   return v->aux;
 }
 \f
@@ -1439,26 +1519,20 @@ cmd_import (void)
   struct import_pgm *pgm = NULL;
   struct file_handle *fh = NULL;
   struct dictionary *dict = NULL;
-  int type;
-
-  pgm = xmalloc (sizeof *pgm);
-  pgm->reader = NULL;
-  pgm->map = NULL;
-  case_nullify (&pgm->bounce);
+  enum pfm_type type;
 
+  lex_match ('/');
   for (;;)
     {
-      lex_match ('/');
-      
-      if (lex_match_id ("FILE") || token == T_STRING)
+      if (pgm == NULL && (lex_match_id ("FILE") || token == T_STRING))
        {
          lex_match ('=');
 
          fh = fh_parse ();
          if (fh == NULL)
-           return CMD_FAILURE;
+            goto error;
        }
-      else if (lex_match_id ("TYPE"))
+      else if (pgm == NULL && lex_match_id ("TYPE"))
        {
          lex_match ('=');
 
@@ -1469,27 +1543,48 @@ cmd_import (void)
          else
            {
              lex_error (_("expecting COMM or TAPE"));
-             return CMD_FAILURE;
+              goto error;
            }
        }
-      else break;
+      else 
+        {
+          if (pgm == NULL) 
+            {
+              if (fh == NULL) 
+                {
+                  lex_sbc_missing ("FILE");
+                  goto error;
+                }
+              
+              discard_variables ();
+
+              pgm = xmalloc (sizeof *pgm);
+              pgm->reader = pfm_open_reader (fh, &dict, NULL);
+              pgm->map = NULL;
+              case_nullify (&pgm->bounce);
+              if (pgm->reader == NULL)
+                goto error;
+
+              case_create (&pgm->bounce, dict_get_next_value_idx (dict));
+  
+              start_case_map (dict);
+            }
+
+          if (token == '.')
+            break;
+          
+          if (!parse_dict_trim (dict))
+            goto error;
+        }
+
+      lex_match ('/');
     }
-  if (!lex_match ('/') && token != '.')
+  if (pgm == NULL) 
     {
       lex_error (NULL);
-      return CMD_FAILURE;
+      goto error;
     }
 
-  discard_variables ();
-
-  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);
@@ -1584,7 +1679,7 @@ struct case_map
    at will before using finish_case_map() to produce the case
    map.
 
-   Uses D's aux members, which may not otherwise be in use. */
+   Uses D's aux members, which must otherwise not be in use. */
 static void
 start_case_map (struct dictionary *d) 
 {
@@ -1618,7 +1713,7 @@ finish_case_map (struct dictionary *d)
 
   map = xmalloc (sizeof *map);
   map->value_cnt = dict_get_next_value_idx (d);
-  map->map = xmalloc (sizeof *map->map * map->value_cnt);
+  map->map = xnmalloc (map->value_cnt, sizeof *map->map);
   for (i = 0; i < map->value_cnt; i++)
     map->map[i] = -1;