Add scratch file handles.
[pspp-builds.git] / src / get.c
index c4dcc820ac5d6e5d5d624849e2a7b0d2ac4748b2..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
@@ -21,6 +21,8 @@
 #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 "hash.h"
 #include "lexer.h"
 #include "misc.h"
-#include "pfm-read.h"
 #include "pfm-write.h"
 #include "settings.h"
-#include "sfm-read.h"
 #include "sfm-write.h"
 #include "str.h"
 #include "value-labels.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. */
@@ -49,111 +52,147 @@ static void map_case (const struct case_map *,
                       const struct ccase *, struct ccase *);
 static void destroy_case_map (struct case_map *);
 
-/* Operation type. */
-enum operation 
+static bool parse_dict_trim (struct dictionary *);
+\f
+/* Reading system and portable files. */
+
+/* Type of command. */
+enum reader_command 
   {
-    OP_READ,    /* GET or IMPORT. */
-    OP_SAVE,    /* SAVE or XSAVE. */
-    OP_EXPORT   /* EXPORT. */
+    GET_CMD,
+    IMPORT_CMD
   };
 
-static bool trim_dictionary (struct dictionary *,
-                             enum operation, int *compress);
-\f
-/* GET input program. */
-struct get_pgm 
+/* Case reader input program. */
+struct case_reader_pgm 
   {
-    struct sfm_reader *reader;  /* System file reader. */
-    struct case_map *map;       /* Map from system file to active file dict. */
+    struct any_reader *reader;  /* File reader. */
+    struct case_map *map;       /* Map from file dict to active file dict. */
     struct ccase bounce;        /* Bounce buffer. */
   };
 
-static void get_pgm_free (struct get_pgm *);
+static const struct case_source_class case_reader_source_class;
 
-/* Parses the GET command. */
-int
-cmd_get (void)
+static void case_reader_pgm_free (struct case_reader_pgm *);
+
+/* Parses a GET or IMPORT command. */
+static int
+parse_read_command (enum reader_command type)
 {
-  struct get_pgm *pgm = NULL;
-  struct file_handle *fh;
+  struct case_reader_pgm *pgm = NULL;
+  struct file_handle *fh = NULL;
   struct dictionary *dict = NULL;
 
-  pgm = xmalloc (sizeof *pgm);
-  pgm->reader = NULL;
-  pgm->map = NULL;
-  case_nullify (&pgm->bounce);
+  for (;;)
+    {
+      lex_match ('/');
 
-  discard_variables ();
+      if (lex_match_id ("FILE") || token == T_STRING)
+       {
+         lex_match ('=');
 
-  lex_match ('/');
-  if (lex_match_id ("FILE"))
-    lex_match ('=');
-  fh = fh_parse ();
-  if (fh == NULL)
-    goto error;
+         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 ('=');
 
-  pgm->reader = sfm_open_reader (fh, &dict, NULL);
+         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 (fh == NULL) 
+    {
+      lex_sbc_missing ("FILE");
+      goto error;
+    }
+              
+  discard_variables ();
+
+  pgm = xmalloc (sizeof *pgm);
+  pgm->reader = any_reader_open (fh, &dict);
+  pgm->map = NULL;
+  case_nullify (&pgm->bounce);
   if (pgm->reader == NULL)
     goto error;
-  case_create (&pgm->bounce, dict_get_next_value_idx (dict));
 
+  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);
 
+  while (token != '.')
+    {
+      lex_match ('/');
+      if (!parse_dict_trim (dict))
+        goto error;
+    }
+
+  pgm->map = finish_case_map (dict);
+  
   dict_destroy (default_dict);
   default_dict = dict;
 
-  vfm_source = create_case_source (&get_source_class, pgm);
+  vfm_source = create_case_source (&case_reader_source_class, pgm);
 
   return CMD_SUCCESS;
 
  error:
-  get_pgm_free (pgm);
+  case_reader_pgm_free (pgm);
   if (dict != NULL)
     dict_destroy (dict);
   return CMD_FAILURE;
 }
 
-/* Frees a struct get_pgm. */
+/* Frees a struct case_reader_pgm. */
 static void
-get_pgm_free (struct get_pgm *pgm) 
+case_reader_pgm_free (struct case_reader_pgm *pgm) 
 {
   if (pgm != NULL) 
     {
-      sfm_close_reader (pgm->reader);
+      any_reader_close (pgm->reader);
       destroy_case_map (pgm->map);
       case_destroy (&pgm->bounce);
       free (pgm);
     }
 }
 
-/* Clears internal state related to GET input procedure. */
+/* Clears internal state related to case reader input procedure. */
 static void
-get_source_destroy (struct case_source *source)
+case_reader_source_destroy (struct case_source *source)
 {
-  struct get_pgm *pgm = source->aux;
-  get_pgm_free (pgm);
+  struct case_reader_pgm *pgm = source->aux;
+  case_reader_pgm_free (pgm);
 }
 
 /* Reads all the cases from the data file into C and passes them
    to WRITE_CASE one by one, passing WC_DATA. */
 static void
-get_source_read (struct case_source *source,
-                 struct ccase *c,
-                 write_case_func *write_case, write_case_data wc_data)
+case_reader_source_read (struct case_source *source,
+                    struct ccase *c,
+                    write_case_func *write_case, write_case_data wc_data)
 {
-  struct get_pgm *pgm = source->aux;
+  struct case_reader_pgm *pgm = source->aux;
   int ok;
 
   do
     {
       if (pgm->map == NULL)
-        ok = sfm_read_case (pgm->reader, c);
+        ok = any_reader_read (pgm->reader, c);
       else
         {
-          ok = sfm_read_case (pgm->reader, &pgm->bounce);
+          ok = any_reader_read (pgm->reader, &pgm->bounce);
           if (ok)
             map_case (pgm->map, &pgm->bounce, c);
         }
@@ -164,198 +203,362 @@ get_source_read (struct case_source *source,
   while (ok);
 }
 
-const struct case_source_class get_source_class =
+static const struct case_source_class case_reader_source_class =
   {
-    "GET",
+    "case reader",
     NULL,
-    get_source_read,
-    get_source_destroy,
+    case_reader_source_read,
+    case_reader_source_destroy,
   };
 \f
-/* XSAVE transformation and SAVE procedure. */
-struct save_trns
+/* GET. */
+int
+cmd_get (void) 
+{
+  return parse_read_command (GET_CMD);
+}
+
+/* IMPORT. */
+int
+cmd_import (void) 
+{
+  return parse_read_command (IMPORT_CMD);
+}
+\f
+/* Writing system and portable files. */ 
+
+/* 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;
+/* 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). */
+  };
 
-/* Parses the SAVE or XSAVE command
-   and returns the parsed transformation. */
-static struct save_trns *
-cmd_save_internal (void)
+/* Destroys AW. */
+static void
+case_writer_destroy (struct case_writer *aw)
 {
-  struct file_handle *fh = NULL;
-  struct dictionary *dict = NULL;
-  struct save_trns *t = NULL;
-  int compress = get_scompression ();
-  const int default_version = 3;
-  int version = default_version;
-  short no_name_table = 0;
-
-  t = xmalloc (sizeof *t);
-  t->h.proc = save_trns_proc;
-  t->h.free = save_trns_free;
-  t->writer = NULL;
-  t->map = NULL;
-  case_nullify (&t->bounce);
-  
+  if (aw != NULL) 
+    {
+      any_writer_close (aw->writer);
+      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 case_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 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;
 
-  /* Read most of the subcommands. */
+  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);
+
+  lex_match ('/');
   for (;;)
     {
-      if (lex_match_id ("VERSION"))
+      if (lex_match_id ("OUTFILE"))
        {
+          if (handle != NULL) 
+            {
+              lex_sbc_only_once ("OUTFILE");
+              goto error; 
+            }
+          
          lex_match ('=');
-         if ( lex_force_num() ) 
-           {
-             lex_get();
-             version = tokval;
-             
-             if ( 0 == strncasecmp (tokid,"x", 1) ) 
-               {
-                 lex_get();
-                 no_name_table = 1;
-               }
-
-           }
+      
+         handle = fh_parse (FH_REF_FILE | FH_REF_SCRATCH);
+         if (handle == NULL)
+           goto error;
        }
-      else if (lex_match_id ("OUTFILE"))
+      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 ('=');
-      
-         fh = fh_parse ();
-         if (fh == NULL)
-           goto error;
-
+         if (!lex_force_int ())
+            goto error;
+          sysfile_opts.version = lex_integer ();
+          lex_get ();
        }
-      if ( ! lex_match('/')  ) 
+      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;
 
-  if (token != '.')
+  if (handle == NULL) 
     {
-      lex_error (_("expecting end of command"));
+      lex_sbc_missing ("OUTFILE");
       goto error;
     }
 
-  if ( fh == NULL ) 
-    {
-      msg ( ME, _("The required %s subcommand was not present"), "OUTFILE");
-      goto error;
-    }
+  dict_compact_values (dict);
+  aw->map = finish_case_map (dict);
+  if (aw->map != NULL)
+    case_create (&aw->bounce, dict_get_next_value_idx (dict));
 
-  if ( version != default_version )
+  if (fh_get_referent (handle) == FH_REF_FILE) 
     {
-      msg (MW, _("Unsupported sysfile version: %d. Using version %d instead."),
-          version, default_version);
-
-      version = default_version;
+      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;
+        }
     }
-
-  dict = dict_clone (default_dict);
-  start_case_map (dict);
-  if (!trim_dictionary (dict, OP_SAVE, &compress))
-    goto error;
-  t->map = finish_case_map (dict);
-  if (t->map != NULL)
-    case_create (&t->bounce, dict_get_next_value_idx (dict));
-
-  t->writer = sfm_open_writer (fh, dict, compress, no_name_table);
-  if (t->writer == NULL)
-    goto error;
-
+  else
+    aw->writer = any_writer_open (handle, dict);
   dict_destroy (dict);
-
-  return t;
+  
+  return aw;
 
  error:
-  assert (t != NULL);
+  case_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
+case_writer_write_case (struct case_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
+  any_writer_write (aw->writer, c);
+}
+\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 case_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);
+
+  case_writer_destroy (aw);
+  return CMD_SUCCESS;
+}
+
+/* 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;
 }
 
-/* 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. */
 
-/* Writes case C to the system file specified on SAVE. */
+/* Transformation. */
+struct output_trns 
+  {
+    struct case_writer *aw;      /* Writer. */
+  };
+
+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_;
+  case_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);
+      case_writer_destroy (t->aw);
+      free (t);
     }
 }
 
+/* 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);
@@ -363,78 +566,39 @@ 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 true on success, false on failure. */
+   appropriately.  Returns true on success, false on failure. */
 static bool
-trim_dictionary (struct dictionary *dict, enum operation op, int *compress)
+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 (lex_match ('/'))
+  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
     {
-      bool ok = true;
-      
-      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"))
-        ok = drop_variables (dict);
-      else if (lex_match_id ("KEEP"))
-       ok = keep_variables (dict);
-      else if (lex_match_id ("RENAME"))
-        ok = rename_variables (dict);
-      else
-       {
-         lex_error (_("expecting a valid subcommand"));
-         ok = false;
-       }
-
-      if (!ok)
-        return false;
+      lex_error (_("expecting a valid subcommand"));
+      return false;
     }
-
-  if (!lex_end_of_command ())
-    return false;
-
-  dict_compact_values (dict);
-  return true;
 }
 
 /* Parses and performs the RENAME subcommand of GET and SAVE. */
 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;
@@ -450,8 +614,6 @@ rename_variables (struct dictionary *dict)
       if (!lex_force_match ('=')
          || !lex_force_id ())
        return 0;
-      if (!strncmp (tokid, v->name, SHORT_NAME_LEN))
-       return 1;
       if (dict_lookup_var (dict, tokid) != NULL)
        {
          msg (SE, _("Cannot rename %s as %s because there already exists "
@@ -473,7 +635,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;
@@ -487,9 +649,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 (')'))
@@ -504,7 +666,7 @@ rename_variables (struct dictionary *dict)
     }
   success = 1;
 
-done:
+ done:
   for (i = 0; i < nn; i++)
     free (new_names[i]);
   free (new_names);
@@ -519,7 +681,7 @@ static bool
 drop_variables (struct dictionary *dict)
 {
   struct variable **v;
-  int nv;
+  size_t nv;
 
   lex_match ('=');
   if (!parse_variables (dict, &v, &nv, PV_NONE))
@@ -541,8 +703,8 @@ static bool
 keep_variables (struct dictionary *dict)
 {
   struct variable **v;
-  int nv;
-  int i;
+  size_t nv;
+  size_t i;
 
   lex_match ('=');
   if (!parse_variables (dict, &v, &nv, PV_NONE))
@@ -552,7 +714,7 @@ keep_variables (struct dictionary *dict)
   dict_reorder_vars (dict, v, nv);
           
   /* Delete the remaining variables. */
-  v = xrealloc (v, (dict_get_var_cnt (dict) - nv) * sizeof *v);
+  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);
@@ -561,91 +723,6 @@ keep_variables (struct dictionary *dict)
   return true;
 }
 \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)
-{
-  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;
-
- error:
-  dict_destroy (dict);
-  export_proc_free (proc);
-  free (proc);
-  return CMD_FAILURE;
-}
-
-/* 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 
-    {
-      map_case (proc->map, c, &proc->bounce);
-      pfm_write_case (proc->writer, &proc->bounce);
-    }
-  return 1;
-}
-
-static void
-export_proc_free (struct export_proc *proc) 
-{
-  if (proc != NULL) 
-    {
-      pfm_close_writer (proc->writer);
-      destroy_case_map (proc->map);
-      case_destroy (&proc->bounce);
-    }
-}
-\f
 /* MATCH FILES. */
 
 #include "debug-print.h"
@@ -660,14 +737,13 @@ 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. */
-    struct sfm_reader *reader;  /* System file reader. */
+    struct any_reader *reader;  /* File reader. */
     struct dictionary *dict;   /* Dictionary from system file. */
 
     /* IN subcommand. */
@@ -686,7 +762,7 @@ struct mtf_proc
     size_t by_cnt;              /* Number of variables on BY subcommand. */
 
     /* Names of FIRST, LAST variables. */
-    char first[SHORT_NAME_LEN + 1], last[SHORT_NAME_LEN + 1];
+    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. */
@@ -734,7 +810,8 @@ cmd_match_files (void)
   dict_set_case_limit (mtf.dict, dict_get_case_limit (default_dict));
 
   lex_match ('/');
-  while (lex_id_match ("FILE", tokid) || lex_id_match ("TABLE", tokid)) 
+  while (token == T_ID
+         && (lex_id_match ("FILE", tokid) || lex_id_match ("TABLE", tokid)))
     {
       struct mtf_file *file = xmalloc (sizeof *file);
 
@@ -747,6 +824,7 @@ cmd_match_files (void)
         }
       else
         assert (0);
+      lex_match ('=');
 
       file->by = NULL;
       file->handle = NULL;
@@ -780,9 +858,7 @@ cmd_match_files (void)
             mtf.head = file;
           first_table->prev = file;
         }
-         
-      lex_match ('=');
-         
+
       if (lex_match ('*'))
         {
           file->handle = NULL;
@@ -817,11 +893,11 @@ cmd_match_files (void)
         }
       else
         {
-          file->handle = fh_parse ();
+          file->handle = fh_parse (FH_REF_FILE | FH_REF_SCRATCH);
           if (file->handle == NULL)
             goto error;
 
-          file->reader = sfm_open_reader (file->handle, &file->dict, NULL);
+          file->reader = any_reader_open (file->handle, &file->dict);
           if (file->reader == NULL)
             goto error;
 
@@ -876,9 +952,9 @@ cmd_match_files (void)
 
           for (iter = mtf.head; iter != NULL; iter = iter->next)
             {
-              int i;
+              size_t i;
          
-              iter->by = xmalloc (sizeof *iter->by * mtf.by_cnt);
+              iter->by = xnmalloc (mtf.by_cnt, sizeof *iter->by);
 
               for (i = 0; i < mtf.by_cnt; i++)
                 {
@@ -886,13 +962,14 @@ cmd_match_files (void)
                   if (iter->by[i] == NULL)
                     {
                       msg (SE, _("File %s lacks BY variable %s."),
-                           iter->handle ? handle_get_name (iter->handle) : "*",
+                           iter->handle ? fh_get_name (iter->handle) : "*",
                            by[i]->name);
                       free (by);
                       goto error;
                     }
                 }
             }
+          free (by);
        }
       else if (lex_match_id ("FIRST")) 
         {
@@ -963,6 +1040,8 @@ cmd_match_files (void)
         }
     }
 
+  /* Set up mapping from each file's variables to master
+     variables. */
   for (iter = mtf.head; iter != NULL; iter = iter->next)
     {
       struct dictionary *d = iter->dict;
@@ -977,20 +1056,20 @@ cmd_match_files (void)
         }
     }
 
+  /* Add IN variables to master dictionary. */
   for (iter = mtf.head; iter != NULL; iter = iter->next) 
     if (iter->in_name != NULL)
       {
-        static const struct fmt_spec f1_0 = {FMT_F, 1, 0};
-        
         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);
+                 iter->in_var->name);
             goto error;
           }
-        iter->in_var->print = iter->in_var->write = f1_0;
+        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.
@@ -1034,7 +1113,7 @@ cmd_match_files (void)
   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);
+  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);
@@ -1042,6 +1121,9 @@ cmd_match_files (void)
     procedure (mtf_processing, &mtf);
   mtf_processing_finish (&mtf);
 
+  free_case_source (vfm_source);
+  vfm_source = NULL;
+
   dict_destroy (default_dict);
   default_dict = mtf.dict;
   mtf.dict = NULL;
@@ -1051,7 +1133,7 @@ cmd_match_files (void)
   mtf_free (&mtf);
   return CMD_SUCCESS;
   
-error:
+ error:
   mtf_free (&mtf);
   return CMD_FAILURE;
 }
@@ -1103,7 +1185,7 @@ static void
 mtf_free_file (struct mtf_file *file)
 {
   free (file->by);
-  sfm_close_reader (file->reader);
+  any_reader_close (file->reader);
   if (file->dict != default_dict)
     dict_destroy (file->dict);
   case_destroy (&file->input);
@@ -1177,7 +1259,7 @@ mtf_read_nonactive_records (void *mtf_)
   for (iter = mtf->head; iter != NULL; iter = next)
     {
       next = iter->next;
-      if (iter->handle && !sfm_read_case (iter->reader, &iter->input))
+      if (iter->handle && !any_reader_read (iter->reader, &iter->input))
         mtf_delete_file_in_place (mtf, &iter);
     }
 }
@@ -1205,7 +1287,9 @@ mtf_processing (struct ccase *c, void *mtf_)
   bool read_active_file;
 
   assert (mtf->head != NULL);
-  assert (mtf->head->type == MTF_FILE);
+  if (mtf->head->type == MTF_TABLE)
+    return 1;
+  
   do
     {
       struct mtf_file *min_head, *min_tail; /* Files with minimum BY values. */
@@ -1220,38 +1304,34 @@ mtf_processing (struct ccase *c, void *mtf_)
       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);
-         }
-
+          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
@@ -1261,32 +1341,28 @@ mtf_processing (struct ccase *c, void *mtf_)
          assert (iter->type == MTF_TABLE);
       
          next = iter->next;
-
-       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);
-           }
+          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. */
@@ -1363,7 +1439,7 @@ mtf_processing (struct ccase *c, void *mtf_)
        {
          next = iter->next_min;
          if (iter->reader != NULL
-              && !sfm_read_case (iter->reader, &iter->input))
+              && !any_reader_read (iter->reader, &iter->input))
             mtf_delete_file_in_place (mtf, &iter);
        }
     }
@@ -1404,13 +1480,14 @@ 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);
 
+      if (dict_class_from_id (dv->name) == DC_SCRATCH)
+        continue;
+
       if (mv != NULL)
         {
           if (mv->width != dv->width) 
@@ -1418,7 +1495,7 @@ mtf_merge_dictionary (struct dictionary *const m, struct mtf_file *f)
               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),
+                   dv->name, fh_get_name (f->handle),
                    var_type_description (dv), var_type_description (mv));
               return 0;
             }
@@ -1428,19 +1505,15 @@ mtf_merge_dictionary (struct dictionary *const m, struct mtf_file *f)
               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_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 (m, dv, dv->name, dv->longname);
-          assert (mv != NULL);
-        }
+        mv = dict_clone_var_assert (m, dv, dv->name);
     }
 
   return 1;
@@ -1461,147 +1534,6 @@ get_master (struct variable *v)
   return v->aux;
 }
 \f
-/* IMPORT command. */
-
-/* IMPORT input program. */
-struct import_pgm 
-  {
-    struct pfm_reader *reader;  /* Portable file reader. */
-    struct case_map *map;       /* Map from system file to active file dict. */
-    struct ccase bounce;        /* Bounce buffer. */
-  };
-
-static void import_pgm_free (struct import_pgm *);
-
-/* Parses the IMPORT command. */
-int
-cmd_import (void)
-{
-  struct 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);
-
-  for (;;)
-    {
-      lex_match ('/');
-      
-      if (lex_match_id ("FILE") || token == T_STRING)
-       {
-         lex_match ('=');
-
-         fh = fh_parse ();
-         if (fh == NULL)
-           return CMD_FAILURE;
-       }
-      else if (lex_match_id ("TYPE"))
-       {
-         lex_match ('=');
-
-         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 != '.')
-    {
-      lex_error (NULL);
-      return CMD_FAILURE;
-    }
-
-  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);
-  default_dict = dict;
-
-  vfm_source = create_case_source (&import_source_class, pgm);
-
-  return CMD_SUCCESS;
-
- error:
-  import_pgm_free (pgm);
-  if (dict != NULL)
-    dict_destroy (dict);
-  return CMD_FAILURE;
-}
-
-/* Frees a struct import_pgm. */
-static void
-import_pgm_free (struct import_pgm *pgm) 
-{
-  if (pgm != NULL) 
-    {
-      pfm_close_reader (pgm->reader);
-      destroy_case_map (pgm->map);
-      case_destroy (&pgm->bounce);
-      free (pgm);
-    }
-}
-
-/* Clears internal state related to IMPORT input procedure. */
-static void
-import_source_destroy (struct case_source *source)
-{
-  struct import_pgm *pgm = source->aux;
-  import_pgm_free (pgm);
-}
-
-/* Reads all the cases from the data file into C and passes them
-   to WRITE_CASE one by one, passing WC_DATA. */
-static void
-import_source_read (struct case_source *source,
-                 struct ccase *c,
-                 write_case_func *write_case, write_case_data wc_data)
-{
-  struct import_pgm *pgm = source->aux;
-  int ok;
-
-  do
-    {
-      if (pgm->map == NULL)
-        ok = pfm_read_case (pgm->reader, c);
-      else
-        {
-          ok = pfm_read_case (pgm->reader, &pgm->bounce);
-          if (ok)
-            map_case (pgm->map, &pgm->bounce, c);
-        }
-
-      if (ok)
-        ok = write_case (wc_data);
-    }
-  while (ok);
-}
-
-const struct case_source_class import_source_class =
-  {
-    "IMPORT",
-    NULL,
-    import_source_read,
-    import_source_destroy,
-  };
 
 \f
 /* Case map.
@@ -1625,7 +1557,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) 
 {
@@ -1659,7 +1591,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;