Adopt use of gnulib for portability.
[pspp-builds.git] / src / data-list.c
index 1a6b9af89f0699417055f34ce65bc7518e170792..2b088860bf71f7493995bb160852db1556828ef4 100644 (file)
 
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
-   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-   02111-1307, USA. */
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA. */
 
 #include <config.h>
-#include <assert.h>
+#include "data-list.h"
+#include "error.h"
 #include <ctype.h>
 #include <float.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include "alloc.h"
+#include "case.h"
 #include "command.h"
 #include "data-in.h"
 #include "debug-print.h"
-#include "dfm.h"
+#include "dfm-read.h"
+#include "dictionary.h"
 #include "error.h"
 #include "file-handle.h"
 #include "format.h"
@@ -38,6 +41,9 @@
 #include "tab.h"
 #include "var.h"
 #include "vfm.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
 \f
 /* Utility function. */
 
 /* Describes how to parse one variable. */
 struct dls_var_spec
   {
-    struct dls_var_spec *next;
+    struct dls_var_spec *next;  /* Next specification in list. */
+
+    /* Both free and fixed formats. */
+    struct fmt_spec input;     /* Input format of this field. */
     struct variable *v;                /* Associated variable.  Used only in
                                   parsing.  Not safe later. */
-    char name[9];              /* Free-format: Name of variable. */
-    int rec;                   /* Fixed-format: Record number (1-based). */
-    int fc, lc;                        /* Fixed-format: Column numbers in record. */
-    struct fmt_spec input;     /* Input format of this field. */
     int fv;                    /* First value in case. */
-    int width;                 /* 0=numeric, >0=width of alpha field. */
+
+    /* Fixed format only. */
+    int rec;                   /* Record number (1-based). */
+    int fc, lc;                        /* Column numbers in record. */
+
+    /* Free format only. */
+    char name[LONG_NAME_LEN + 1]; /* Name of variable. */
   };
 
 /* Constants for DATA LIST type. */
@@ -73,31 +84,28 @@ enum
 struct data_list_pgm
   {
     struct trns_header h;
-    struct dls_var_spec *spec; /* Variable parsing specifications. */
-    struct file_handle *handle;        /* Input file, never NULL. */
-    /* Do not reorder preceding fields. */
+
+    struct dls_var_spec *first, *last; /* Variable parsing specifications. */
+    struct dfm_reader *reader;  /* Data file reader. */
 
     int type;                  /* A DLS_* constant. */
     struct variable *end;      /* Variable specified on END subcommand. */
     int eof;                   /* End of file encountered. */
-    int nrec;                  /* Number of records. */
+    int rec_cnt;                /* Number of records. */
+    size_t case_size;           /* Case size in bytes. */
+    char *delims;               /* Delimiters if any; not null-terminated. */
+    size_t delim_cnt;           /* Number of delimiter, or 0 for spaces. */
   };
 
-/* Holds information on parsing the data file. */
-static struct data_list_pgm dls;
-
-/* Pointer to a pointer to where the first dls_var_spec should go. */
-static struct dls_var_spec **first;
-
-/* Last dls_var_spec in the chain.  Used for building the linked-list. */
-static struct dls_var_spec *next;
-
-static int parse_fixed (void);
-static int parse_free (void);
-static void dump_fixed_table (void);
-static void dump_free_table (void);
-static void destroy_dls (struct trns_header *);
-static int read_one_case (struct trns_header *, struct ccase *);
+static int parse_fixed (struct data_list_pgm *);
+static int parse_free (struct dls_var_spec **, struct dls_var_spec **);
+static void dump_fixed_table (const struct dls_var_spec *,
+                              const struct file_handle *, int rec_cnt);
+static void dump_free_table (const struct data_list_pgm *,
+                             const struct file_handle *);
+static void destroy_dls_var_spec (struct dls_var_spec *);
+static trns_free_func data_list_trns_free;
+static trns_proc_func data_list_trns_proc;
 
 /* Message title for REPEATING DATA. */
 #define RPD_ERR "REPEATING DATA: "
@@ -105,38 +113,37 @@ static int read_one_case (struct trns_header *, struct ccase *);
 int
 cmd_data_list (void)
 {
-  /* 0=print no table, 1=print table.  (TABLE subcommand.)  */
-  int table = -1;
-
-  lex_match_id ("DATA");
-  lex_match_id ("LIST");
+  struct data_list_pgm *dls;     /* DATA LIST program under construction. */
+  int table = -1;                /* Print table if nonzero, -1=undecided. */
+  struct file_handle *fh = NULL; /* File handle of source, NULL=inline file. */
 
-  if (vfm_source != &input_program_source
-      && vfm_source != &file_type_source)
+  if (!case_source_is_complex (vfm_source))
     discard_variables ();
 
-  dls.handle = default_handle;
-  dls.type = -1;
-  dls.end = NULL;
-  dls.eof = 0;
-  dls.nrec = 0;
-  dls.spec = NULL;
-  next = NULL;
-  first = &dls.spec;
+  dls = xmalloc (sizeof *dls);
+  dls->reader = NULL;
+  dls->type = -1;
+  dls->end = NULL;
+  dls->eof = 0;
+  dls->rec_cnt = 0;
+  dls->delims = NULL;
+  dls->delim_cnt = 0;
+  dls->first = dls->last = NULL;
 
   while (token != '/')
     {
       if (lex_match_id ("FILE"))
        {
          lex_match ('=');
-         dls.handle = fh_parse_file_handle ();
-         if (!dls.handle)
-           return CMD_FAILURE;
-         if (vfm_source == &file_type_source && dls.handle != default_handle)
+         fh = fh_parse ();
+         if (fh == NULL)
+           goto error;
+         if (case_source_is_class (vfm_source, &file_type_source_class)
+              && fh != default_handle)
            {
              msg (SE, _("DATA LIST may not use a different file from "
                         "that specified on its surrounding FILE TYPE."));
-             return CMD_FAILURE;
+             goto error;
            }
        }
       else if (lex_match_id ("RECORDS"))
@@ -144,126 +151,155 @@ cmd_data_list (void)
          lex_match ('=');
          lex_match ('(');
          if (!lex_force_int ())
-           return CMD_FAILURE;
-         dls.nrec = lex_integer ();
+           goto error;
+         dls->rec_cnt = lex_integer ();
          lex_get ();
          lex_match (')');
        }
       else if (lex_match_id ("END"))
        {
-         if (dls.end)
+         if (dls->end)
            {
              msg (SE, _("The END subcommand may only be specified once."));
-             return CMD_FAILURE;
+             goto error;
            }
          
          lex_match ('=');
          if (!lex_force_id ())
-           return CMD_FAILURE;
-         dls.end = dict_lookup_var (default_dict, tokid);
-         if (!dls.end) 
-            {
-              dls.end = dict_create_var (default_dict, tokid, 0);
-              assert (dls.end != NULL);
-            }
+           goto error;
+         dls->end = dict_lookup_var (default_dict, tokid);
+         if (!dls->end) 
+            dls->end = dict_create_var_assert (default_dict, tokid, 0);
          lex_get ();
        }
       else if (token == T_ID)
        {
-         /* Must match DLS_* constants. */
-         static const char *id[] = {"FIXED", "FREE", "LIST", "NOTABLE",
-                                    "TABLE", NULL};
-         const char **p;
-         int index;
-
-         for (p = id; *p; p++)
-           if (lex_id_match (*p, tokid))
-             break;
-         if (*p == NULL)
-           {
-             lex_error (NULL);
-             return CMD_FAILURE;
-           }
-         
-         lex_get ();
-
-         index = p - id;
-         if (index < 3)
-           {
-             if (dls.type != -1)
+          if (lex_match_id ("NOTABLE"))
+            table = 0;
+          else if (lex_match_id ("TABLE"))
+            table = 1;
+          else 
+            {
+              int type;
+              if (lex_match_id ("FIXED"))
+                type = DLS_FIXED;
+              else if (lex_match_id ("FREE"))
+                type = DLS_FREE;
+              else if (lex_match_id ("LIST"))
+                type = DLS_LIST;
+              else 
+                {
+                  lex_error (NULL);
+                  goto error;
+                }
+
+             if (dls->type != -1)
                {
                  msg (SE, _("Only one of FIXED, FREE, or LIST may "
-                           "be specified."));
-                 return CMD_FAILURE;
+                             "be specified."));
+                 goto error;
                }
-             
-             dls.type = index;
-           }
-         else
-           table = index - 3;
-       }
+             dls->type = type;
+
+              if ((dls->type == DLS_FREE || dls->type == DLS_LIST)
+                  && lex_match ('(')) 
+                {
+                  while (!lex_match (')'))
+                    {
+                      int delim;
+
+                      if (lex_match_id ("TAB"))
+                        delim = '\t';
+                      else if (token == T_STRING && tokstr.length == 1)
+                       {
+                         delim = tokstr.string[0];
+                         lex_get();
+                       }
+                      else 
+                        {
+                          lex_error (NULL);
+                          goto error;
+                        }
+
+                      dls->delims = xrealloc (dls->delims, dls->delim_cnt + 1);
+                      dls->delims[dls->delim_cnt++] = delim;
+
+                      lex_match (',');
+                    }
+                }
+            }
+        }
       else
        {
          lex_error (NULL);
-         return CMD_FAILURE;
+         goto error;
        }
     }
 
-  default_handle = dls.handle;
+  dls->case_size = dict_get_case_size (default_dict);
+  default_handle = fh;
 
-  if (dls.type == -1)
-    dls.type = DLS_FIXED;
+  if (dls->type == -1)
+    dls->type = DLS_FIXED;
 
   if (table == -1)
     {
-      if (dls.type == DLS_FREE)
+      if (dls->type == DLS_FREE)
        table = 0;
       else
        table = 1;
     }
 
-  if (dls.type == DLS_FIXED)
+  if (dls->type == DLS_FIXED)
     {
-      if (!parse_fixed ())
-       return CMD_FAILURE;
+      if (!parse_fixed (dls))
+       goto error;
       if (table)
-       dump_fixed_table ();
+       dump_fixed_table (dls->first, fh, dls->rec_cnt);
     }
   else
     {
-      if (!parse_free ())
-       return CMD_FAILURE;
+      if (!parse_free (&dls->first, &dls->last))
+       goto error;
       if (table)
-       dump_free_table ();
+       dump_free_table (dls, fh);
     }
 
+  dls->reader = dfm_open_reader (fh);
+  if (dls->reader == NULL)
+    goto error;
+
   if (vfm_source != NULL)
     {
-      struct data_list_pgm *new_pgm;
-
-      dls.h.proc = read_one_case;
-      dls.h.free = destroy_dls;
-
-      new_pgm = xmalloc (sizeof *new_pgm);
-      memcpy (new_pgm, &dls, sizeof *new_pgm);
-      add_transformation ((struct trns_header *) new_pgm);
+      dls->h.proc = data_list_trns_proc;
+      dls->h.free = data_list_trns_free;
+      add_transformation (&dls->h);
     }
-  else
-    vfm_source = &data_list_source;
+  else 
+    vfm_source = create_case_source (&data_list_source_class, dls);
 
   return CMD_SUCCESS;
+
+ error:
+  destroy_dls_var_spec (dls->first);
+  free (dls->delims);
+  free (dls);
+  return CMD_FAILURE;
 }
 
+/* Adds SPEC to the linked list with head at FIRST and tail at
+   LAST. */
 static void
-append_var_spec (struct dls_var_spec *spec)
+append_var_spec (struct dls_var_spec **first, struct dls_var_spec **last,
+                 struct dls_var_spec *spec)
 {
-  if (next == 0)
-    *first = next = xmalloc (sizeof *spec);
-  else
-    next = next->next = xmalloc (sizeof *spec);
+  spec->next = NULL;
 
-  memcpy (next, spec, sizeof *spec);
-  next->next = NULL;
+  if (*first == NULL)
+    *first = spec;
+  else 
+    (*last)->next = spec;
+  *last = spec;
 }
 \f
 /* Fixed-format parsing. */
@@ -277,33 +313,30 @@ struct fmt_list
     struct fmt_list *down;
   };
 
-/* Used as "local" variables among the fixed-format parsing funcs.  If
-   it were guaranteed that PSPP were going to be compiled by gcc,
-   I'd make all these functions a single set of nested functions. */
-static struct
+/* State of parsing DATA LIST. */
+struct fixed_parsing_state
   {
     char **name;               /* Variable names. */
-    int nname;                 /* Number of names. */
-    int cname;                 /* dump_fmt_list: index of next name to use. */
+    int name_cnt;              /* Number of names. */
 
     int recno;                 /* Index of current record. */
     int sc;                    /* 1-based column number of starting column for
                                   next field to output. */
+  };
 
-    struct dls_var_spec spec;  /* Next specification to output. */
-    int fc, lc;                        /* First, last column in set of fields specified
-                                  together. */
-
-    int level;                 /* Nesting level in fixed_parse_fortran(). */
-  }
-fx;
-
-static int fixed_parse_compatible (void);
-static struct fmt_list *fixed_parse_fortran (void);
+static int fixed_parse_compatible (struct fixed_parsing_state *,
+                                   struct dls_var_spec **,
+                                   struct dls_var_spec **);
+static int fixed_parse_fortran (struct fixed_parsing_state *,
+                                struct dls_var_spec **,
+                                struct dls_var_spec **);
 
+/* Parses all the variable specifications for DATA LIST FIXED,
+   storing them into DLS.  Returns nonzero if successful. */
 static int
-parse_fixed (void)
+parse_fixed (struct data_list_pgm *dls)
 {
+  struct fixed_parsing_state fx;
   int i;
 
   fx.recno = 0;
@@ -314,7 +347,7 @@ parse_fixed (void)
       while (lex_match ('/'))
        {
          fx.recno++;
-         if (lex_integer_p ())
+         if (lex_is_integer ())
            {
              if (lex_integer () < fx.recno)
                {
@@ -331,85 +364,89 @@ parse_fixed (void)
            }
          fx.sc = 1;
        }
-      fx.spec.rec = fx.recno;
 
-      if (!parse_DATA_LIST_vars (&fx.name, &fx.nname, PV_NONE))
+      if (!parse_DATA_LIST_vars (&fx.name, &fx.name_cnt, PV_NONE))
        return 0;
 
-      if (token == T_NUM)
+      if (lex_is_number ())
        {
-         if (!fixed_parse_compatible ())
+         if (!fixed_parse_compatible (&fx, &dls->first, &dls->last))
            goto fail;
        }
       else if (token == '(')
        {
-         fx.level = 0;
-         fx.cname = 0;
-         if (!fixed_parse_fortran ())
+         if (!fixed_parse_fortran (&fx, &dls->first, &dls->last))
            goto fail;
        }
       else
        {
          msg (SE, _("SPSS-like or FORTRAN-like format "
-              "specification expected after variable names."));
+                     "specification expected after variable names."));
          goto fail;
        }
 
-      for (i = 0; i < fx.nname; i++)
+      for (i = 0; i < fx.name_cnt; i++)
        free (fx.name[i]);
       free (fx.name);
     }
-  if (dls.nrec && next->rec > dls.nrec)
+  if (dls->first == NULL) 
     {
-      msg (SE, _("Variables are specified on records that "
-                "should not exist according to RECORDS subcommand."));
+      msg (SE, _("At least one variable must be specified."));
       return 0;
     }
-  else if (!dls.nrec)
-    dls.nrec = next->rec;
-  if (token != '.')
+  if (dls->rec_cnt && dls->last->rec > dls->rec_cnt)
     {
-      lex_error (_("expecting end of command"));
+      msg (SE, _("Variables are specified on records that "
+                "should not exist according to RECORDS subcommand."));
       return 0;
     }
-  return 1;
+  else if (!dls->rec_cnt)
+    dls->rec_cnt = dls->last->rec;
+  return lex_end_of_command () == CMD_SUCCESS;
 
 fail:
-  for (i = 0; i < fx.nname; i++)
+  for (i = 0; i < fx.name_cnt; i++)
     free (fx.name[i]);
   free (fx.name);
   return 0;
 }
 
+/* Parses a variable specification in the form 1-10 (A) based on
+   FX and adds specifications to the linked list with head at
+   FIRST and tail at LAST. */
 static int
-fixed_parse_compatible (void)
+fixed_parse_compatible (struct fixed_parsing_state *fx,
+                        struct dls_var_spec **first, struct dls_var_spec **last)
 {
-  int dividend;
+  struct fmt_spec input;
+  int fc, lc;
+  int width;
   int i;
 
+  /* First column. */
   if (!lex_force_int ())
     return 0;
-  
-  fx.fc = lex_integer ();
-  if (fx.fc < 1)
+  fc = lex_integer ();
+  if (fc < 1)
     {
       msg (SE, _("Column positions for fields must be positive."));
       return 0;
     }
   lex_get ();
 
+  /* Last column. */
   lex_negative_to_dash ();
   if (lex_match ('-'))
     {
       if (!lex_force_int ())
        return 0;
-      fx.lc = lex_integer ();
-      if (fx.lc < 1)
+      lc = lex_integer ();
+      if (lc < 1)
        {
          msg (SE, _("Column positions for fields must be positive."));
          return 0;
        }
-      else if (fx.lc < fx.fc)
+      else if (lc < fc)
        {
          msg (SE, _("The ending column for a field must be "
                     "greater than the starting column."));
@@ -419,9 +456,19 @@ fixed_parse_compatible (void)
       lex_get ();
     }
   else
-    fx.lc = fx.fc;
+    lc = fc;
+
+  /* Divide columns evenly. */
+  input.w = (lc - fc + 1) / fx->name_cnt;
+  if ((lc - fc + 1) % fx->name_cnt)
+    {
+      msg (SE, _("The %d columns %d-%d "
+                "can't be evenly divided into %d fields."),
+          lc - fc + 1, fc, lc, fx->name_cnt);
+      return 0;
+    }
 
-  fx.spec.input.w = fx.lc - fx.fc + 1;
+  /* Format specifier. */
   if (lex_match ('('))
     {
       struct fmt_desc *fdp;
@@ -430,8 +477,8 @@ fixed_parse_compatible (void)
        {
          const char *cp;
 
-         fx.spec.input.type = parse_format_specifier_name (&cp, 0);
-         if (fx.spec.input.type == -1)
+         input.type = parse_format_specifier_name (&cp, 0);
+         if (input.type == -1)
            return 0;
          if (*cp)
            {
@@ -444,9 +491,9 @@ fixed_parse_compatible (void)
          lex_match (',');
        }
       else
-       fx.spec.input.type = FMT_F;
+       input.type = FMT_F;
 
-      if (lex_integer_p ())
+      if (lex_is_integer ())
        {
          if (lex_integer () < 1)
            {
@@ -455,105 +502,94 @@ fixed_parse_compatible (void)
              return 0;
            }
          
-         fx.spec.input.d = lex_integer ();
+         input.d = lex_integer ();
          lex_get ();
        }
       else
-       fx.spec.input.d = 0;
+       input.d = 0;
 
-      fdp = &formats[fx.spec.input.type];
-      if (fdp->n_args < 2 && fx.spec.input.d)
+      fdp = &formats[input.type];
+      if (fdp->n_args < 2 && input.d)
        {
          msg (SE, _("Input format %s doesn't accept decimal places."),
               fdp->name);
          return 0;
        }
       
-      if (fx.spec.input.d > 16)
-       fx.spec.input.d = 16;
+      if (input.d > 16)
+       input.d = 16;
 
       if (!lex_force_match (')'))
        return 0;
     }
   else
     {
-      fx.spec.input.type = FMT_F;
-      fx.spec.input.d = 0;
+      input.type = FMT_F;
+      input.d = 0;
     }
+  if (!check_input_specifier (&input, 1))
+    return 0;
 
-  fx.sc = fx.lc + 1;
+  /* Start column for next specification. */
+  fx->sc = lc + 1;
 
-  if ((fx.lc - fx.fc + 1) % fx.nname)
-    {
-      msg (SE, _("The %d columns %d-%d "
-                "can't be evenly divided into %d fields."),
-          fx.lc - fx.fc + 1, fx.fc, fx.lc, fx.nname);
-      return 0;
-    }
-
-  dividend = (fx.lc - fx.fc + 1) / fx.nname;
-  fx.spec.input.w = dividend;
-  if (!check_input_specifier (&fx.spec.input))
-    return 0;
+  /* Width of variables to create. */
+  if (input.type == FMT_A || input.type == FMT_AHEX) 
+    width = input.w;
+  else
+    width = 0;
 
-  for (i = 0; i < fx.nname; i++)
+  /* Create variables and var specs. */
+  for (i = 0; i < fx->name_cnt; i++)
     {
-      int type;
-      int width;
+      struct dls_var_spec *spec;
       struct variable *v;
 
-      if (fx.spec.input.type == FMT_A || fx.spec.input.type == FMT_AHEX) 
-        {
-          type = ALPHA;
-          width = dividend; 
-        }
-      else 
-        {
-          type = NUMERIC;
-          width = 0;
-        }
-
-      v = dict_create_var (default_dict, fx.name[i], width);
-      if (v)
+      v = dict_create_var (default_dict, fx->name[i], width);
+      if (v != NULL)
        {
-         convert_fmt_ItoO (&fx.spec.input, &v->print);
+         convert_fmt_ItoO (&input, &v->print);
          v->write = v->print;
+          if (!case_source_is_complex (vfm_source))
+            v->init = 0;
        }
       else
        {
-         v = dict_lookup_var (default_dict, fx.name[i]);
-         assert (v != NULL);
-         if (!vfm_source)
+         v = dict_lookup_var_assert (default_dict, fx->name[i]);
+         if (vfm_source == NULL)
            {
-             msg (SE, _("%s is a duplicate variable name."), fx.name[i]);
+             msg (SE, _("%s is a duplicate variable name."), fx->name[i]);
              return 0;
            }
-         if (type != v->type)
+         if ((width != 0) != (v->width != 0))
            {
              msg (SE, _("There is already a variable %s of a "
                         "different type."),
-                  fx.name[i]);
+                  fx->name[i]);
              return 0;
            }
-         if (type == ALPHA && dividend != v->width)
+         if (width != 0 && width != v->width)
            {
              msg (SE, _("There is already a string variable %s of a "
-                        "different width."), fx.name[i]);
+                        "different width."), fx->name[i]);
              return 0;
            }
        }
 
-      fx.spec.v = v;
-      fx.spec.fc = fx.fc + dividend * i;
-      fx.spec.lc = fx.spec.fc + dividend - 1;
-      fx.spec.fv = v->fv;
-      fx.spec.width = v->width;
-      append_var_spec (&fx.spec);
+      spec = xmalloc (sizeof *spec);
+      spec->input = input;
+      spec->v = v;
+      spec->fv = v->fv;
+      spec->rec = fx->recno;
+      spec->fc = fc + input.w * i;
+      spec->lc = spec->fc + input.w - 1;
+      append_var_spec (first, last, spec);
     }
   return 1;
 }
 
-/* Destroy a format list and, optionally, all its sublists. */
+/* Destroy format list F and, if RECURSE is nonzero, all its
+   sublists. */
 static void
 destroy_fmt_list (struct fmt_list *f, int recurse)
 {
@@ -569,146 +605,167 @@ destroy_fmt_list (struct fmt_list *f, int recurse)
 }
 
 /* Takes a hierarchically structured fmt_list F as constructed by
-   fixed_parse_fortran(), and flattens it into a linear list of
-   dls_var_spec's. */
+   fixed_parse_fortran(), and flattens it, adding the variable
+   specifications to the linked list with head FIRST and tail
+   LAST.  NAME_IDX is used to take values from the list of names
+   in FX; it should initially point to a value of 0. */
 static int
-dump_fmt_list (struct fmt_list *f)
+dump_fmt_list (struct fixed_parsing_state *fx, struct fmt_list *f,
+               struct dls_var_spec **first, struct dls_var_spec **last,
+               int *name_idx)
 {
   int i;
 
   for (; f; f = f->next)
     if (f->f.type == FMT_X)
-      fx.sc += f->count;
+      fx->sc += f->count;
     else if (f->f.type == FMT_T)
-      fx.sc = f->f.w;
+      fx->sc = f->f.w;
     else if (f->f.type == FMT_NEWREC)
       {
-       fx.recno += f->count;
-       fx.sc = 1;
+       fx->recno += f->count;
+       fx->sc = 1;
       }
     else
       for (i = 0; i < f->count; i++)
        if (f->f.type == FMT_DESCEND)
          {
-           if (!dump_fmt_list (f->down))
+           if (!dump_fmt_list (fx, f->down, first, last, name_idx))
              return 0;
          }
        else
          {
-           int type;
+            struct dls_var_spec *spec;
             int width;
            struct variable *v;
 
             if (formats[f->f.type].cat & FCAT_STRING) 
-              {
-                type = ALPHA;
-                width = f->f.w;
-              }
-            else 
-              {
-                type = NUMERIC;
-                width = 0;
-              }
-           if (fx.cname >= fx.nname)
+              width = f->f.w;
+            else
+              width = 0;
+           if (*name_idx >= fx->name_cnt)
              {
                msg (SE, _("The number of format "
-                          "specifications exceeds the number of "
-                          "variable names given."));
+                          "specifications exceeds the given number of "
+                          "variable names."));
                return 0;
              }
            
-           fx.spec.v = v = dict_create_var (default_dict,
-                                            fx.name[fx.cname++],
-                                            width);
+           v = dict_create_var (default_dict, fx->name[(*name_idx)++], width);
            if (!v)
              {
-               msg (SE, _("%s is a duplicate variable name."), fx.name[i]);
+               msg (SE, _("%s is a duplicate variable name."), fx->name[i]);
                return 0;
              }
            
-           fx.spec.input = f->f;
-           convert_fmt_ItoO (&fx.spec.input, &v->print);
+            if (!case_source_is_complex (vfm_source))
+              v->init = 0;
+
+            spec = xmalloc (sizeof *spec);
+            spec->v = v;
+           spec->input = f->f;
+           spec->fv = v->fv;
+           spec->rec = fx->recno;
+           spec->fc = fx->sc;
+           spec->lc = fx->sc + f->f.w - 1;
+           append_var_spec (first, last, spec);
+
+           convert_fmt_ItoO (&spec->input, &v->print);
            v->write = v->print;
 
-           fx.spec.rec = fx.recno;
-           fx.spec.fc = fx.sc;
-           fx.spec.lc = fx.sc + f->f.w - 1;
-           fx.spec.fv = v->fv;
-           fx.spec.width = v->width;
-           append_var_spec (&fx.spec);
-
-           fx.sc += f->f.w;
+           fx->sc += f->f.w;
          }
   return 1;
 }
 
-/* Calls itself recursively to parse nested levels of parentheses.
-   Returns to its original caller: NULL, to indicate error; non-NULL,
-   but nothing useful, to indicate success (it returns a free()'d
-   block). */
+/* Recursively parses a FORTRAN-like format specification into
+   the linked list with head FIRST and tail TAIL.  LEVEL is the
+   level of recursion, starting from 0.  Returns the parsed
+   specification if successful, or a null pointer on failure.  */
 static struct fmt_list *
-fixed_parse_fortran (void)
+fixed_parse_fortran_internal (struct fixed_parsing_state *fx,
+                              struct dls_var_spec **first,
+                              struct dls_var_spec **last)
 {
-  struct fmt_list *head;
-  struct fmt_list *fl = NULL;
+  struct fmt_list *head = NULL;
+  struct fmt_list *tail = NULL;
 
-  lex_get ();                  /* Skip opening parenthesis. */
+  lex_force_match ('(');
   while (token != ')')
     {
-      if (fl)
-       fl = fl->next = xmalloc (sizeof *fl);
+      /* New fmt_list. */
+      struct fmt_list *new = xmalloc (sizeof *new);
+      new->next = NULL;
+
+      /* Append new to list. */
+      if (head != NULL)
+       tail->next = new;
       else
-       head = fl = xmalloc (sizeof *fl);
+       head = new;
+      tail = new;
 
-      if (lex_integer_p ())
+      /* Parse count. */
+      if (lex_is_integer ())
        {
-         fl->count = lex_integer ();
+         new->count = lex_integer ();
          lex_get ();
        }
       else
-       fl->count = 1;
+       new->count = 1;
 
+      /* Parse format specifier. */
       if (token == '(')
        {
-         fl->f.type = FMT_DESCEND;
-         fx.level++;
-         fl->down = fixed_parse_fortran ();
-         fx.level--;
-         if (!fl->down)
+         new->f.type = FMT_DESCEND;
+         new->down = fixed_parse_fortran_internal (fx, first, last);
+         if (new->down == NULL)
            goto fail;
        }
       else if (lex_match ('/'))
-       fl->f.type = FMT_NEWREC;
-      else if (!parse_format_specifier (&fl->f, 1)
-              || !check_input_specifier (&fl->f))
+       new->f.type = FMT_NEWREC;
+      else if (!parse_format_specifier (&new->f, FMTP_ALLOW_XT)
+              || !check_input_specifier (&new->f, 1))
        goto fail;
 
       lex_match (',');
     }
-  fl->next = NULL;
-  lex_get ();
+  lex_force_match (')');
 
-  if (fx.level)
-    return head;
-
-  fl->next = NULL;
-  dump_fmt_list (head);
-  if (fx.cname < fx.nname)
-    {
-      msg (SE, _("There aren't enough format specifications "
-          "to match the number of variable names given."));
-      goto fail;
-    }
-  destroy_fmt_list (head, 1);
   return head;
 
 fail:
-  fl->next = NULL;
   destroy_fmt_list (head, 0);
 
   return NULL;
 }
 
+/* Parses a FORTRAN-like format specification into the linked
+   list with head FIRST and tail LAST.  Returns nonzero if
+   successful. */
+static int
+fixed_parse_fortran (struct fixed_parsing_state *fx,
+                     struct dls_var_spec **first, struct dls_var_spec **last)
+{
+  struct fmt_list *list;
+  int name_idx;
+
+  list = fixed_parse_fortran_internal (fx, first, last);
+  if (list == NULL)
+    return 0;
+  
+  name_idx = 0;
+  dump_fmt_list (fx, list, first, last, &name_idx);
+  destroy_fmt_list (list, 1);
+  if (name_idx < fx->name_cnt)
+    {
+      msg (SE, _("There aren't enough format specifications "
+                 "to match the number of variable names given."));
+      return 0; 
+    }
+
+  return 1;
+}
+
 /* Displays a table giving information on fixed-format variable
    parsing on DATA LIST. */
 /* FIXME: The `Columns' column should be divided into three columns,
@@ -716,15 +773,14 @@ fail:
    column; then right-justify the starting column and left-justify the
    ending column. */
 static void
-dump_fixed_table (void)
+dump_fixed_table (const struct dls_var_spec *specs,
+                  const struct file_handle *fh, int rec_cnt)
 {
-  struct dls_var_spec *spec;
+  const struct dls_var_spec *spec;
   struct tab_table *t;
-  char *buf;
-  const char *filename;
   int i;
 
-  for (i = 0, spec = *first; spec; spec = spec->next)
+  for (i = 0, spec = specs; spec; spec = spec->next)
     i++;
   t = tab_create (4, i + 1, 0);
   tab_columns (t, TAB_COL_DOWN, 1);
@@ -737,7 +793,7 @@ dump_fixed_table (void)
   tab_hline (t, TAL_2, 0, 3, 1);
   tab_dim (t, tab_natural_dimensions);
 
-  for (i = 1, spec = *first; spec; spec = spec->next, i++)
+  for (i = 1, spec = specs; spec; spec = spec->next, i++)
     {
       tab_text (t, 0, i, TAB_LEFT, spec->v->name);
       tab_text (t, 1, i, TAT_PRINTF, "%d", spec->rec);
@@ -747,119 +803,106 @@ dump_fixed_table (void)
                    fmt_to_string (&spec->input));
     }
 
-  if (*first == dls.spec)
-    {
-      filename = fh_handle_name (dls.handle);
-      if (filename == NULL)
-       filename = "";
-      buf = local_alloc (strlen (filename) + INT_DIGITS + 80);
-      sprintf (buf, (dls.handle != inline_file
-                    ? 
-                    ngettext("Reading %d record from file %s.",
-                             "Reading %d records from file %s.",dls.nrec)
-                    : 
-                    ngettext("Reading %d record from the command file.",
-                             "Reading %d records from the command file.",
-                             dls.nrec)),
-              dls.nrec, filename);
-    }
+  if (fh != NULL)
+    tab_title (t, 1, ngettext ("Reading %d record from file %s.",
+                               "Reading %d records from file %s.", rec_cnt),
+               rec_cnt, handle_get_filename (fh));
   else
-    {
-      buf = local_alloc (strlen (_("Occurrence data specifications.")) + 1);
-      strcpy (buf, _("Occurrence data specifications."));
-    }
-  
-  tab_title (t, 0, buf);
+    tab_title (t, 1, ngettext ("Reading %d record from the command file.",
+                               "Reading %d records from the command file.",
+                               rec_cnt),
+               rec_cnt);
   tab_submit (t);
-  fh_handle_name (NULL);
-  local_free (buf);
 }
 \f
 /* Free-format parsing. */
 
+/* Parses variable specifications for DATA LIST FREE and adds
+   them to the linked list with head FIRST and tail LAST.
+   Returns nonzero only if successful. */
 static int
-parse_free (void)
+parse_free (struct dls_var_spec **first, struct dls_var_spec **last)
 {
-  struct dls_var_spec spec;
-  struct fmt_spec in, out;
-  char **name;
-  int nname;
-  int i;
-
   lex_get ();
   while (token != '.')
     {
+      struct fmt_spec input, output;
+      char **name;
+      int name_cnt;
       int width;
+      int i;
 
-      if (!parse_DATA_LIST_vars (&name, &nname, PV_NONE))
+      if (!parse_DATA_LIST_vars (&name, &name_cnt, PV_NONE))
        return 0;
+
       if (lex_match ('('))
        {
-         if (!parse_format_specifier (&in, 0) || !check_input_specifier (&in))
-           goto fail;
-         if (!lex_force_match (')'))
-           goto fail;
-         convert_fmt_ItoO (&in, &out);
+         if (!parse_format_specifier (&input, 0)
+              || !check_input_specifier (&input, 1)
+              || !lex_force_match (')')) 
+            {
+              for (i = 0; i < name_cnt; i++)
+                free (name[i]);
+              free (name);
+              return 0; 
+            }
+         convert_fmt_ItoO (&input, &output);
        }
       else
        {
          lex_match ('*');
-         in.type = FMT_F;
-         in.w = 8;
-         in.d = 0;
-         out = set_format;
+          input = make_input_format (FMT_F, 8, 0);
+         output = get_format ();
        }
 
-      spec.input = in;
-      if (in.type == FMT_A || in.type == FMT_AHEX)
-       width = in.w;
+      if (input.type == FMT_A || input.type == FMT_AHEX)
+       width = input.w;
       else
        width = 0;
-      for (i = 0; i < nname; i++)
+      for (i = 0; i < name_cnt; i++)
        {
+          struct dls_var_spec *spec;
          struct variable *v;
 
-         spec.v = v = dict_create_var (default_dict, name[i], width);
+         v = dict_create_var (default_dict, name[i], width);
+         
          if (!v)
            {
              msg (SE, _("%s is a duplicate variable name."), name[i]);
              return 0;
            }
-         
-         v->print = v->write = out;
+         v->print = v->write = output;
+
+          if (!case_source_is_complex (vfm_source))
+            v->init = 0;
 
-         strcpy (spec.name, name[i]);
-         spec.fv = v->fv;
-         spec.width = width;
-         append_var_spec (&spec);
+          spec = xmalloc (sizeof *spec);
+          spec->input = input;
+          spec->v = v;
+         spec->fv = v->fv;
+         str_copy_trunc (spec->name, sizeof spec->name, v->name);
+         append_var_spec (first, last, spec);
        }
-      for (i = 0; i < nname; i++)
+      for (i = 0; i < name_cnt; i++)
        free (name[i]);
       free (name);
     }
 
-  if (token != '.')
-    lex_error (_("expecting end of command"));
-  return 1;
-
-fail:
-  for (i = 0; i < nname; i++)
-    free (name[i]);
-  free (name);
-  return 0;
+  return lex_end_of_command () == CMD_SUCCESS;
 }
 
 /* Displays a table giving information on free-format variable parsing
    on DATA LIST. */
 static void
-dump_free_table (void)
+dump_free_table (const struct data_list_pgm *dls,
+                 const struct file_handle *fh)
 {
   struct tab_table *t;
   int i;
   
   {
     struct dls_var_spec *spec;
-    for (i = 0, spec = dls.spec; spec; spec = spec->next)
+    for (i = 0, spec = dls->first; spec; spec = spec->next)
       i++;
   }
   
@@ -875,242 +918,233 @@ dump_free_table (void)
   {
     struct dls_var_spec *spec;
     
-    for (i = 1, spec = dls.spec; spec; spec = spec->next, i++)
+    for (i = 1, spec = dls->first; spec; spec = spec->next, i++)
       {
        tab_text (t, 0, i, TAB_LEFT, spec->v->name);
        tab_text (t, 1, i, TAB_LEFT | TAT_FIX, fmt_to_string (&spec->input));
       }
   }
-  
-  {
-    const char *filename;
-
-    filename = fh_handle_name (dls.handle);
-    if (filename == NULL)
-      filename = "";
-    tab_title (t, 1,
-              (dls.handle != inline_file
-               ? _("Reading free-form data from file %s.")
-               : _("Reading free-form data from the command file.")),
-              filename);
-  }
+
+  if (fh != NULL)
+    tab_title (t, 1, _("Reading free-form data from file %s."),
+               handle_get_filename (fh));
+  else
+    tab_title (t, 1, _("Reading free-form data from the command file."));
   
   tab_submit (t);
-  fh_handle_name (NULL);
 }
 \f
 /* Input procedure. */ 
 
-/* Pointer to relevant parsing data.  Static just to avoid passing it
-   around so much. */
-static struct data_list_pgm *dlsp;
-
-/* Extracts a field from the current position in the current record.
-   Fields can be unquoted or quoted with single- or double-quote
-   characters.  *RET_LEN is set to the field length, *RET_CP is set to
-   the field itself.  After parsing the field, sets the current
-   position in the record to just past the field.  Returns 0 on
-   failure or a 1-based column number indicating the beginning of the
-   field on success. */
+/* Extracts a field from the current position in the current
+   record.  Fields can be unquoted or quoted with single- or
+   double-quote characters.  *FIELD is set to the field content.
+   After parsing the field, sets the current position in the
+   record to just past the field and any trailing delimiter.
+   END_BLANK is used internally; it should be initialized by the
+   caller to 0 and left alone afterward.  Returns 0 on failure or
+   a 1-based column number indicating the beginning of the field
+   on success. */
 static int
-cut_field (char **ret_cp, int *ret_len)
+cut_field (const struct data_list_pgm *dls, struct fixed_string *field,
+           int *end_blank)
 {
-  char *cp, *ep;
-  int len;
-
-  cp = dfm_get_record (dlsp->handle, &len);
-  if (!cp)
-    return 0;
-
-  ep = cp + len;
+  struct fixed_string line;
+  char *cp;
+  size_t column_start;
 
-  /* Skip leading whitespace and commas. */
-  while ((isspace ((unsigned char) *cp) || *cp == ',') && cp < ep)
-    cp++;
-  if (cp >= ep)
+  if (dfm_eof (dls->reader))
     return 0;
+  if (dls->delim_cnt == 0)
+    dfm_expand_tabs (dls->reader);
+  dfm_get_record (dls->reader, &line);
 
-  /* Three types of fields: quoted with ', quoted with ", unquoted. */
-  if (*cp == '\'' || *cp == '"')
+  cp = ls_c_str (&line);
+  if (dls->delim_cnt == 0) 
     {
-      int quote = *cp;
-
-      *ret_cp = ++cp;
-      while (cp < ep && *cp != quote)
-       cp++;
-      *ret_len = cp - *ret_cp;
-      if (cp < ep)
-       cp++;
+      /* Skip leading whitespace. */
+      while (cp < ls_end (&line) && isspace ((unsigned char) *cp))
+        cp++;
+      if (cp >= ls_end (&line))
+        return 0;
+      
+      /* Handle actual data, whether quoted or unquoted. */
+      if (*cp == '\'' || *cp == '"')
+        {
+          int quote = *cp;
+
+          field->string = ++cp;
+          while (cp < ls_end (&line) && *cp != quote)
+            cp++;
+          field->length = cp - field->string;
+          if (cp < ls_end (&line))
+            cp++;
+          else
+            msg (SW, _("Quoted string missing terminating `%c'."), quote);
+        }
       else
-       msg (SW, _("Scope of string exceeds line."));
+        {
+          field->string = cp;
+          while (cp < ls_end (&line)
+                 && !isspace ((unsigned char) *cp) && *cp != ',')
+            cp++;
+          field->length = cp - field->string;
+        }
+
+      /* Skip trailing whitespace and a single comma if present. */
+      while (cp < ls_end (&line) && isspace ((unsigned char) *cp))
+        cp++;
+      if (cp < ls_end (&line) && *cp == ',')
+        cp++;
     }
-  else
+  else 
     {
-      *ret_cp = cp;
-      while (cp < ep && !isspace ((unsigned char) *cp) && *cp != ',')
-       cp++;
-      *ret_len = cp - *ret_cp;
+      if (cp >= ls_end (&line)) 
+        {
+          int column = dfm_column_start (dls->reader);
+               /* A blank line or a line that ends in \t has a
+             trailing blank field. */
+          if (column == 1 || (column > 1 && cp[-1] == '\t'))
+            {
+              if (*end_blank == 0)
+                {
+                  *end_blank = 1;
+                  field->string = ls_end (&line);
+                  field->length = 0;
+                  dfm_forward_record (dls->reader);
+                  return column;
+                }
+              else 
+                {
+                  *end_blank = 0;
+                  return 0;
+                }
+            }
+          else 
+            return 0;
+        }
+      else 
+        {
+          field->string = cp;
+          while (cp < ls_end (&line)
+                 && memchr (dls->delims, *cp, dls->delim_cnt) == NULL)
+            cp++; 
+          field->length = cp - field->string;
+          if (cp < ls_end (&line)) 
+            cp++;
+        }
     }
-
-  {
-    int beginning_column;
-    
-    dfm_set_record (dlsp->handle, *ret_cp);
-    beginning_column = dfm_get_cur_col (dlsp->handle) + 1;
+  
+  dfm_forward_columns (dls->reader, field->string - line.string);
+  column_start = dfm_column_start (dls->reader);
     
-    dfm_set_record (dlsp->handle, cp);
+  dfm_forward_columns (dls->reader, cp - field->string);
     
-    return beginning_column;
-  }
+  return column_start;
 }
 
-static int read_from_data_list_fixed (void);
-static int read_from_data_list_free (void);
-static int read_from_data_list_list (void);
-static int do_reading (int flag);
+typedef int data_list_read_func (const struct data_list_pgm *, struct ccase *);
+static data_list_read_func read_from_data_list_fixed;
+static data_list_read_func read_from_data_list_free;
+static data_list_read_func read_from_data_list_list;
 
-/* FLAG==0: reads any number of cases into temp_case and calls
-   write_case() for each one, returns garbage.  FLAG!=0: reads one
-   case into temp_case and returns -2 on eof, -1 otherwise.
-   Uses dlsp as the relevant parsing description. */
-static int
-do_reading (int flag)
+/* Returns the proper function to read the kind of DATA LIST
+   data specified by DLS. */
+static data_list_read_func *
+get_data_list_read_func (const struct data_list_pgm *dls) 
 {
-  int (*func) (void);
-
-  int code;
-
-  dfm_push (dlsp->handle);
-
-  switch (dlsp->type)
+  switch (dls->type)
     {
     case DLS_FIXED:
-      func = read_from_data_list_fixed;
-      break;
+      return read_from_data_list_fixed;
+
     case DLS_FREE:
-      func = read_from_data_list_free;
-      break;
+      return read_from_data_list_free;
+
     case DLS_LIST:
-      func = read_from_data_list_list;
-      break;
+      return read_from_data_list_list;
+
     default:
       assert (0);
+      abort ();
     }
-  if (flag)
-    {
-      code = func ();
-      if (code == -2)
-       {
-         if (dlsp->eof == 1)
-           {
-             msg (SE, _("Attempt to read past end of file."));
-             err_failure ();
-             return -2;
-           }
-         dlsp->eof = 1;
-       }
-      else
-       dlsp->eof = 0;
-
-      if (dlsp->end != NULL)
-       {
-         if (code == -2)
-           {
-             printf ("end of file, setting %s to 1\n", dlsp->end->name);
-             temp_case->data[dlsp->end->fv].f = 1.0;
-             code = -1;
-           }
-         else
-           {
-             printf ("not end of file, setting %s to 0\n", dlsp->end->name);
-             temp_case->data[dlsp->end->fv].f = 0.0;
-           }
-       }
-    }
-  else
-    {
-      while (func () != -2)
-       if (!write_case ())
-         {
-           debug_printf ((_("abort in write_case()\n")));
-           break;
-         }
-      fh_close_handle (dlsp->handle);
-    }
-  dfm_pop (dlsp->handle);
-
-  return code;
 }
 
-/* Reads a case from the data file and parses it according to
-   fixed-format syntax rules. */
+/* Reads a case from the data file into C, parsing it according
+   to fixed-format syntax rules in DLS.  Returns -1 on success,
+   -2 at end of file. */
 static int
-read_from_data_list_fixed (void)
+read_from_data_list_fixed (const struct data_list_pgm *dls,
+                           struct ccase *c)
 {
-  struct dls_var_spec *var_spec = dlsp->spec;
+  struct dls_var_spec *var_spec = dls->first;
   int i;
 
-  if (!dfm_get_record (dlsp->handle, NULL))
+  if (dfm_eof (dls->reader))
     return -2;
-  for (i = 1; i <= dlsp->nrec; i++)
+  for (i = 1; i <= dls->rec_cnt; i++)
     {
-      int len;
-      char *line = dfm_get_record (dlsp->handle, &len);
+      struct fixed_string line;
       
-      if (!line)
+      if (dfm_eof (dls->reader))
        {
          /* Note that this can't occur on the first record. */
          msg (SW, _("Partial case of %d of %d records discarded."),
-              i - 1, dlsp->nrec);
+              i - 1, dls->rec_cnt);
          return -2;
        }
+      dfm_expand_tabs (dls->reader);
+      dfm_get_record (dls->reader, &line);
 
       for (; var_spec && i == var_spec->rec; var_spec = var_spec->next)
        {
          struct data_in di;
 
-         data_in_finite_line (&di, line, len, var_spec->fc, var_spec->lc);
-         di.v = &temp_case->data[var_spec->fv];
-         di.flags = 0;
+         data_in_finite_line (&di, ls_c_str (&line), ls_length (&line),
+                               var_spec->fc, var_spec->lc);
+         di.v = case_data_rw (c, var_spec->fv);
+         di.flags = DI_IMPLIED_DECIMALS;
          di.f1 = var_spec->fc;
          di.format = var_spec->input;
 
          data_in (&di);
        }
 
-      dfm_fwd_record (dlsp->handle);
+      dfm_forward_record (dls->reader);
     }
 
   return -1;
 }
 
-/* Reads a case from the data file and parses it according to
-   free-format syntax rules. */
+/* Reads a case from the data file into C, parsing it according
+   to free-format syntax rules in DLS.  Returns -1 on success,
+   -2 at end of file. */
 static int
-read_from_data_list_free (void)
+read_from_data_list_free (const struct data_list_pgm *dls,
+                          struct ccase *c)
 {
   struct dls_var_spec *var_spec;
-  char *field;
-  int len;
+  int end_blank = 0;
 
-  for (var_spec = dlsp->spec; var_spec; var_spec = var_spec->next)
+  for (var_spec = dls->first; var_spec; var_spec = var_spec->next)
     {
+      struct fixed_string field;
       int column;
       
       /* Cut out a field and read in a new record if necessary. */
       for (;;)
        {
-         column = cut_field (&field, &len);
+         column = cut_field (dls, &field, &end_blank);
          if (column != 0)
            break;
 
-         if (dfm_get_record (dlsp->handle, NULL))
-           dfm_fwd_record (dlsp->handle);
-         if (!dfm_get_record (dlsp->handle, NULL))
+         if (!dfm_eof (dls->reader)) 
+            dfm_forward_record (dls->reader);
+         if (dfm_eof (dls->reader))
            {
-             if (var_spec != dlsp->spec)
+             if (var_spec != dls->first)
                msg (SW, _("Partial case discarded.  The first variable "
-                    "missing was %s."), var_spec->name);
+                           "missing was %s."), var_spec->name);
              return -2;
            }
        }
@@ -1118,9 +1152,9 @@ read_from_data_list_free (void)
       {
        struct data_in di;
 
-       di.s = field;
-       di.e = field + len;
-       di.v = &temp_case->data[var_spec->fv];
+       di.s = ls_c_str (&field);
+       di.e = ls_end (&field);
+       di.v = case_data_rw (c, var_spec->fv);
        di.flags = 0;
        di.f1 = column;
        di.format = var_spec->input;
@@ -1131,43 +1165,49 @@ read_from_data_list_free (void)
 }
 
 /* Reads a case from the data file and parses it according to
-   list-format syntax rules. */
+   list-format syntax rules.  Returns -1 on success, -2 at end of
+   file. */
 static int
-read_from_data_list_list (void)
+read_from_data_list_list (const struct data_list_pgm *dls,
+                          struct ccase *c)
 {
   struct dls_var_spec *var_spec;
-  char *field;
-  int len;
+  int end_blank = 0;
 
-  if (!dfm_get_record (dlsp->handle, NULL))
+  if (dfm_eof (dls->reader))
     return -2;
 
-  for (var_spec = dlsp->spec; var_spec; var_spec = var_spec->next)
+  for (var_spec = dls->first; var_spec; var_spec = var_spec->next)
     {
+      struct fixed_string field;
+      int column;
+
       /* Cut out a field and check for end-of-line. */
-      int column = cut_field (&field, &len);
-      
+      column = cut_field (dls, &field, &end_blank);
       if (column == 0)
        {
-         if (set_undefined)
+         if (get_undefined ())
            msg (SW, _("Missing value(s) for all variables from %s onward.  "
-                "These will be filled with the system-missing value "
-                "or blanks, as appropriate."),
+                       "These will be filled with the system-missing value "
+                       "or blanks, as appropriate."),
                 var_spec->name);
          for (; var_spec; var_spec = var_spec->next)
-           if (var_spec->width == 0)
-             temp_case->data[var_spec->fv].f = SYSMIS;
-           else
-             memset (temp_case->data[var_spec->fv].s, ' ', var_spec->width);
+            {
+              int width = get_format_var_width (&var_spec->input);
+              if (width == 0)
+                case_data_rw (c, var_spec->fv)->f = SYSMIS;
+              else
+                memset (case_data_rw (c, var_spec->fv)->s, ' ', width); 
+            }
          break;
        }
       
       {
        struct data_in di;
 
-       di.s = field;
-       di.e = field + len;
-       di.v = &temp_case->data[var_spec->fv];
+       di.s = ls_c_str (&field);
+       di.e = ls_end (&field);
+       di.v = case_data_rw (c, var_spec->fv);
        di.flags = 0;
        di.f1 = column;
        di.format = var_spec->input;
@@ -1175,61 +1215,115 @@ read_from_data_list_list (void)
       }
     }
 
-  dfm_fwd_record (dlsp->handle);
+  dfm_forward_record (dls->reader);
   return -1;
 }
 
-/* Destroys DATA LIST transformation or input program PGM. */
+/* Destroys SPEC. */
 static void
-destroy_dls (struct trns_header *pgm)
+destroy_dls_var_spec (struct dls_var_spec *spec) 
 {
-  struct data_list_pgm *dls = (struct data_list_pgm *) pgm;
-  struct dls_var_spec *iter, *next;
+  struct dls_var_spec *next;
 
-  iter = dls->spec;
-  while (iter)
+  while (spec != NULL)
     {
-      next = iter->next;
-      free (iter);
-      iter = next;
+      next = spec->next;
+      free (spec);
+      spec = next;
     }
-  fh_close_handle (dls->handle);
 }
 
-/* Note that since this is exclusively an input program, C is
-   guaranteed to be temp_case. */
+/* Destroys DATA LIST transformation PGM. */
+static void
+data_list_trns_free (struct trns_header *pgm)
+{
+  struct data_list_pgm *dls = (struct data_list_pgm *) pgm;
+  free (dls->delims);
+  destroy_dls_var_spec (dls->first);
+  dfm_close_reader (dls->reader);
+}
+
+/* Handle DATA LIST transformation T, parsing data into C. */
 static int
-read_one_case (struct trns_header *t, struct ccase *c UNUSED)
+data_list_trns_proc (struct trns_header *t, struct ccase *c,
+                     int case_num UNUSED)
 {
-  dlsp = (struct data_list_pgm *) t;
-  return do_reading (1);
+  struct data_list_pgm *dls = (struct data_list_pgm *) t;
+  data_list_read_func *read_func;
+  int retval;
+
+  dfm_push (dls->reader);
+
+  read_func = get_data_list_read_func (dls);
+  retval = read_func (dls, c);
+
+  /* Handle end of file. */
+  if (retval == -2)
+    {
+      /* If we already encountered end of file then this is an
+         error. */
+      if (dls->eof == 1)
+        {
+          msg (SE, _("Attempt to read past end of file."));
+          err_failure ();
+          dfm_pop (dls->reader);
+          return -2;
+        }
+
+      /* Otherwise simply note it. */
+      dls->eof = 1;
+    }
+  else
+    dls->eof = 0;
+
+  /* If there was an END subcommand handle it. */
+  if (dls->end != NULL) 
+    {
+      if (retval == -2)
+        {
+          case_data_rw (c, dls->end->fv)->f = 1.0;
+          retval = -1;
+        }
+      else
+        case_data_rw (c, dls->end->fv)->f = 0.0;
+    }
+  
+  dfm_pop (dls->reader);
+
+  return retval;
 }
 \f
 /* Reads all the records from the data file and passes them to
    write_case(). */
 static void
-data_list_source_read (void)
+data_list_source_read (struct case_source *source,
+                       struct ccase *c,
+                       write_case_func *write_case, write_case_data wc_data)
 {
-  dlsp = &dls;
-  do_reading (0);
+  struct data_list_pgm *dls = source->aux;
+  data_list_read_func *read_func = get_data_list_read_func (dls);
+
+  dfm_push (dls->reader);
+  while (read_func (dls, c) != -2)
+    if (!write_case (wc_data))
+      break;
+  dfm_pop (dls->reader);
 }
 
 /* Destroys the source's internal data. */
 static void
-data_list_source_destroy_source (void)
+data_list_source_destroy (struct case_source *source)
 {
-  destroy_dls ((struct trns_header *) & dls);
+  data_list_trns_free (source->aux);
+  free (source->aux);
 }
 
-struct case_stream data_list_source = 
+const struct case_source_class data_list_source_class = 
   {
+    "DATA LIST",
     NULL,
     data_list_source_read,
-    NULL,
-    NULL,
-    data_list_source_destroy_source,
-    NULL,
-    "DATA LIST",
+    data_list_source_destroy,
   };
 \f
 /* REPEATING DATA. */
@@ -1245,9 +1339,8 @@ struct rpd_num_or_var
 struct repeating_data_trns
   {
     struct trns_header h;
-    struct dls_var_spec *spec; /* Variable parsing specifications. */
-    struct file_handle *handle;        /* Input file, never NULL. */
-    /* Do not reorder preceding fields. */
+    struct dls_var_spec *first, *last; /* Variable parsing specifications. */
+    struct dfm_reader *reader;         /* Input file, never NULL. */
 
     struct rpd_num_or_var starts_beg;  /* STARTS=, before the dash. */
     struct rpd_num_or_var starts_end;  /* STARTS=, after the dash. */
@@ -1255,17 +1348,21 @@ struct repeating_data_trns
     struct rpd_num_or_var length;      /* LENGTH= subcommand. */
     struct rpd_num_or_var cont_beg;    /* CONTINUED=, before the dash. */
     struct rpd_num_or_var cont_end;    /* CONTINUED=, after the dash. */
-    int id_beg, id_end;                        /* ID subcommand, beginning & end columns. */
-    struct variable *id_var;           /* ID subcommand, DATA LIST variable. */
-    struct fmt_spec id_spec;           /* ID subcommand, input format spec. */
-  };
 
-/* Information about the transformation being parsed. */
-static struct repeating_data_trns rpd;
+    /* ID subcommand. */
+    int id_beg, id_end;                        /* Beginning & end columns. */
+    struct variable *id_var;           /* DATA LIST variable. */
+    struct fmt_spec id_spec;           /* Input format spec. */
+    union value *id_value;              /* ID value. */
 
-static int read_one_set_of_repetitions (struct trns_header *, struct ccase *);
+    write_case_func *write_case;
+    write_case_data wc_data;
+  };
+
+static trns_free_func repeating_data_trns_free;
 static int parse_num_or_var (struct rpd_num_or_var *, const char *);
-static int parse_repeating_data (void);
+static int parse_repeating_data (struct dls_var_spec **,
+                                 struct dls_var_spec **);
 static void find_variable_input_spec (struct variable *v,
                                      struct fmt_spec *spec);
 
@@ -1273,28 +1370,27 @@ static void find_variable_input_spec (struct variable *v,
 int
 cmd_repeating_data (void)
 {
-  /* 0=print no table, 1=print table.  (TABLE subcommand.)  */
-  int table = 1;
-
-  /* Bits are set when a particular subcommand has been seen. */
-  unsigned seen = 0;
+  struct repeating_data_trns *rpd;
+  int table = 1;                /* Print table? */
+  bool saw_starts = false;      /* Saw STARTS subcommand? */
+  bool saw_occurs = false;      /* Saw OCCURS subcommand? */
+  bool saw_length = false;      /* Saw LENGTH subcommand? */
+  bool saw_continued = false;   /* Saw CONTINUED subcommand? */
+  bool saw_id = false;          /* Saw ID subcommand? */
+  struct file_handle *const fh = default_handle;
   
-  lex_match_id ("REPEATING");
-  lex_match_id ("DATA");
-
-  assert (vfm_source == &input_program_source
-         || vfm_source == &file_type_source);
-  
-  rpd.handle = default_handle;
-  rpd.starts_beg.num = 0;
-  rpd.starts_beg.var = NULL;
-  rpd.starts_end = rpd.occurs = rpd.length = rpd.cont_beg
-    = rpd.cont_end = rpd.starts_beg;
-  rpd.id_beg = rpd.id_end = 0;
-  rpd.id_var = NULL;
-  rpd.spec = NULL;
-  first = &rpd.spec;
-  next = NULL;
+  assert (case_source_is_complex (vfm_source));
+
+  rpd = xmalloc (sizeof *rpd);
+  rpd->reader = dfm_open_reader (default_handle);
+  rpd->first = rpd->last = NULL;
+  rpd->starts_beg.num = 0;
+  rpd->starts_beg.var = NULL;
+  rpd->starts_end = rpd->occurs = rpd->length = rpd->cont_beg
+    = rpd->cont_end = rpd->starts_beg;
+  rpd->id_beg = rpd->id_end = 0;
+  rpd->id_var = NULL;
+  rpd->id_value = NULL;
 
   lex_match ('/');
   
@@ -1302,130 +1398,131 @@ cmd_repeating_data (void)
     {
       if (lex_match_id ("FILE"))
        {
+          struct file_handle *file;
          lex_match ('=');
-         rpd.handle = fh_parse_file_handle ();
-         if (!rpd.handle)
-           return CMD_FAILURE;
-         if (rpd.handle != default_handle)
+         file = fh_parse ();
+         if (file == NULL)
+           goto error;
+         if (file != fh)
            {
              msg (SE, _("REPEATING DATA must use the same file as its "
                         "corresponding DATA LIST or FILE TYPE."));
-             return CMD_FAILURE;
+              goto error;
            }
        }
       else if (lex_match_id ("STARTS"))
        {
          lex_match ('=');
-         if (seen & 1)
+         if (saw_starts)
            {
              msg (SE, _("%s subcommand given multiple times."),"STARTS");
-             return CMD_FAILURE;
+             goto error;
            }
-         seen |= 1;
-
-         if (!parse_num_or_var (&rpd.starts_beg, "STARTS beginning column"))
-           return CMD_FAILURE;
+          saw_starts = true;
+          
+         if (!parse_num_or_var (&rpd->starts_beg, "STARTS beginning column"))
+           goto error;
 
          lex_negative_to_dash ();
          if (lex_match ('-'))
            {
-             if (!parse_num_or_var (&rpd.starts_end, "STARTS ending column"))
-               return CMD_FAILURE;
+             if (!parse_num_or_var (&rpd->starts_end, "STARTS ending column"))
+               goto error;
            } else {
-             /* Otherwise, rpd.starts_end is left uninitialized.
-                This is okay.  We will initialize it later from the
-                record length of the file.  We can't do this now
-                because we can't be sure that the user has specified
-                the file handle yet. */
+             /* Otherwise, rpd->starts_end is uninitialized.  We
+                will initialize it later from the record length
+                of the file.  We can't do so now because the
+                file handle may not be specified yet. */
            }
 
-         if (rpd.starts_beg.num != 0 && rpd.starts_end.num != 0
-             && rpd.starts_beg.num > rpd.starts_end.num)
+         if (rpd->starts_beg.num != 0 && rpd->starts_end.num != 0
+             && rpd->starts_beg.num > rpd->starts_end.num)
            {
              msg (SE, _("STARTS beginning column (%d) exceeds "
                         "STARTS ending column (%d)."),
-                  rpd.starts_beg.num, rpd.starts_end.num);
-             return CMD_FAILURE;
+                  rpd->starts_beg.num, rpd->starts_end.num);
+             goto error;
            }
        }
       else if (lex_match_id ("OCCURS"))
        {
          lex_match ('=');
-         if (seen & 2)
+         if (saw_occurs)
            {
              msg (SE, _("%s subcommand given multiple times."),"OCCURS");
-             return CMD_FAILURE;
+             goto error;
            }
-         seen |= 2;
+         saw_occurs |= 2;
 
-         if (!parse_num_or_var (&rpd.occurs, "OCCURS"))
-           return CMD_FAILURE;
+         if (!parse_num_or_var (&rpd->occurs, "OCCURS"))
+           goto error;
        }
       else if (lex_match_id ("LENGTH"))
        {
          lex_match ('=');
-         if (seen & 4)
+         if (saw_length & 4)
            {
              msg (SE, _("%s subcommand given multiple times."),"LENGTH");
-             return CMD_FAILURE;
+             goto error;
            }
-         seen |= 4;
+         saw_length |= 4;
 
-         if (!parse_num_or_var (&rpd.length, "LENGTH"))
-           return CMD_FAILURE;
+         if (!parse_num_or_var (&rpd->length, "LENGTH"))
+           goto error;
        }
       else if (lex_match_id ("CONTINUED"))
        {
          lex_match ('=');
-         if (seen & 8)
+         if (saw_continued & 8)
            {
              msg (SE, _("%s subcommand given multiple times."),"CONTINUED");
-             return CMD_FAILURE;
+             goto error;
            }
-         seen |= 8;
+         saw_continued |= 8;
 
          if (!lex_match ('/'))
            {
-             if (!parse_num_or_var (&rpd.cont_beg, "CONTINUED beginning column"))
-               return CMD_FAILURE;
+             if (!parse_num_or_var (&rpd->cont_beg,
+                                     "CONTINUED beginning column"))
+               goto error;
 
              lex_negative_to_dash ();
              if (lex_match ('-')
-                 && !parse_num_or_var (&rpd.cont_end,
+                 && !parse_num_or_var (&rpd->cont_end,
                                        "CONTINUED ending column"))
-               return CMD_FAILURE;
+               goto error;
          
-             if (rpd.cont_beg.num != 0 && rpd.cont_end.num != 0
-                 && rpd.cont_beg.num > rpd.cont_end.num)
+             if (rpd->cont_beg.num != 0 && rpd->cont_end.num != 0
+                 && rpd->cont_beg.num > rpd->cont_end.num)
                {
                  msg (SE, _("CONTINUED beginning column (%d) exceeds "
                             "CONTINUED ending column (%d)."),
-                      rpd.cont_beg.num, rpd.cont_end.num);
-                 return CMD_FAILURE;
+                      rpd->cont_beg.num, rpd->cont_end.num);
+                 goto error;
                }
            }
          else
-           rpd.cont_beg.num = 1;
+           rpd->cont_beg.num = 1;
        }
       else if (lex_match_id ("ID"))
        {
          lex_match ('=');
-         if (seen & 16)
+         if (saw_id & 16)
            {
              msg (SE, _("%s subcommand given multiple times."),"ID");
-             return CMD_FAILURE;
+             goto error;
            }
-         seen |= 16;
+         saw_id |= 16;
          
          if (!lex_force_int ())
-           return CMD_FAILURE;
+           goto error;
          if (lex_integer () < 1)
            {
              msg (SE, _("ID beginning column (%ld) must be positive."),
                   lex_integer ());
-             return CMD_FAILURE;
+             goto error;
            }
-         rpd.id_beg = lex_integer ();
+         rpd->id_beg = lex_integer ();
          
          lex_get ();
          lex_negative_to_dash ();
@@ -1433,33 +1530,34 @@ cmd_repeating_data (void)
          if (lex_match ('-'))
            {
              if (!lex_force_int ())
-               return CMD_FAILURE;
+               goto error;
              if (lex_integer () < 1)
                {
                  msg (SE, _("ID ending column (%ld) must be positive."),
                       lex_integer ());
-                 return CMD_FAILURE;
+                 goto error;
                }
-             if (lex_integer () < rpd.id_end)
+             if (lex_integer () < rpd->id_end)
                {
                  msg (SE, _("ID ending column (%ld) cannot be less than "
                             "ID beginning column (%d)."),
-                      lex_integer (), rpd.id_beg);
-                 return CMD_FAILURE;
+                      lex_integer (), rpd->id_beg);
+                 goto error;
                }
              
-             rpd.id_end = lex_integer ();
+             rpd->id_end = lex_integer ();
              lex_get ();
            }
-         else rpd.id_end = rpd.id_beg;
+         else rpd->id_end = rpd->id_beg;
 
          if (!lex_force_match ('='))
-           return CMD_FAILURE;
-         rpd.id_var = parse_variable ();
-         if (rpd.id_var == NULL)
-           return CMD_FAILURE;
+           goto error;
+         rpd->id_var = parse_variable ();
+         if (rpd->id_var == NULL)
+           goto error;
 
-         find_variable_input_spec (rpd.id_var, &rpd.id_spec);
+         find_variable_input_spec (rpd->id_var, &rpd->id_spec);
+          rpd->id_value = xmalloc (sizeof *rpd->id_value * rpd->id_var->nv);
        }
       else if (lex_match_id ("TABLE"))
        table = 1;
@@ -1470,74 +1568,91 @@ cmd_repeating_data (void)
       else
        {
          lex_error (NULL);
-         return CMD_FAILURE;
+         goto error;
        }
 
       if (!lex_force_match ('/'))
-       return CMD_FAILURE;
+       goto error;
     }
 
   /* Comes here when DATA specification encountered. */
-  if ((seen & (1 | 2)) != (1 | 2))
+  if (!saw_starts || !saw_occurs)
     {
-      if ((seen & 1) == 0)
+      if (!saw_starts)
        msg (SE, _("Missing required specification STARTS."));
-      if ((seen & 2) == 0)
+      if (!saw_occurs)
        msg (SE, _("Missing required specification OCCURS."));
-      return CMD_FAILURE;
+      goto error;
     }
 
   /* Enforce ID restriction. */
-  if ((seen & 16) && !(seen & 8))
+  if (saw_id && !saw_continued)
     {
       msg (SE, _("ID specified without CONTINUED."));
-      return CMD_FAILURE;
+      goto error;
     }
 
-  /* Calculate starts_end, cont_end if necessary. */
-  if (rpd.starts_end.num == 0 && rpd.starts_end.var == NULL)
-    rpd.starts_end.num = fh_record_width (rpd.handle);
-  if (rpd.cont_end.num == 0 && rpd.starts_end.var == NULL)
-    rpd.cont_end.num = fh_record_width (rpd.handle);
-      
-  /* Calculate length if possible. */
-  if ((seen & 4) == 0)
+  /* Calculate and check starts_end, cont_end if necessary. */
+  if (rpd->starts_end.num == 0 && rpd->starts_end.var == NULL) 
     {
-      struct dls_var_spec *iter;
-      
-      for (iter = rpd.spec; iter; iter = iter->next)
-       {
-         if (iter->lc > rpd.length.num)
-           rpd.length.num = iter->lc;
-       }
-      assert (rpd.length.num != 0);
+      rpd->starts_end.num = fh != NULL ? handle_get_record_width (fh) : 80;
+      if (rpd->starts_beg.num != 0 
+          && rpd->starts_beg.num > rpd->starts_end.num)
+        {
+          msg (SE, _("STARTS beginning column (%d) exceeds "
+                     "default STARTS ending column taken from file's "
+                     "record width (%d)."),
+               rpd->starts_beg.num, rpd->starts_end.num);
+          goto error;
+        } 
+    }
+  if (rpd->cont_end.num == 0 && rpd->cont_end.var == NULL) 
+    {
+      rpd->cont_end.num = fh != NULL ? handle_get_record_width (fh) : 80;
+      if (rpd->cont_beg.num != 0
+          && rpd->cont_beg.num > rpd->cont_end.num)
+        {
+          msg (SE, _("CONTINUED beginning column (%d) exceeds "
+                     "default CONTINUED ending column taken from file's "
+                     "record width (%d)."),
+               rpd->cont_beg.num, rpd->cont_end.num);
+          goto error;
+        } 
     }
   
   lex_match ('=');
-  if (!parse_repeating_data ())
-    return CMD_FAILURE;
+  if (!parse_repeating_data (&rpd->first, &rpd->last))
+    goto error;
 
+  /* Calculate length if necessary. */
+  if (!saw_length)
+    {
+      struct dls_var_spec *iter;
+      
+      for (iter = rpd->first; iter; iter = iter->next)
+        if (iter->lc > rpd->length.num)
+          rpd->length.num = iter->lc;
+      assert (rpd->length.num != 0);
+    }
+  
   if (table)
-    dump_fixed_table ();
+    dump_fixed_table (rpd->first, fh, rpd->last->rec);
 
-  {
-    struct repeating_data_trns *new_trns;
-
-    rpd.h.proc = read_one_set_of_repetitions;
-    rpd.h.free = destroy_dls;
-
-    new_trns = xmalloc (sizeof *new_trns);
-    memcpy (new_trns, &rpd, sizeof *new_trns);
-    add_transformation ((struct trns_header *) new_trns);
-  }
+  rpd->h.proc = repeating_data_trns_proc;
+  rpd->h.free = repeating_data_trns_free;
+  add_transformation (&rpd->h);
 
   return lex_end_of_command ();
+
+ error:
+  destroy_dls_var_spec (rpd->first);
+  free (rpd->id_value);
+  return CMD_FAILURE;
 }
 
-/* Because of the way that DATA LIST is structured, it's not trivial
-   to determine what input format is associated with a given variable.
-   This function finds the input format specification for variable V
-   and puts it in SPEC. */
+/* Finds the input format specification for variable V and puts
+   it in SPEC.  Because of the way that DATA LIST is structured,
+   this is nontrivial. */
 static void 
 find_variable_input_spec (struct variable *v, struct fmt_spec *spec)
 {
@@ -1547,11 +1662,11 @@ find_variable_input_spec (struct variable *v, struct fmt_spec *spec)
     {
       struct data_list_pgm *pgm = (struct data_list_pgm *) t_trns[i];
       
-      if (pgm->h.proc == read_one_case)
+      if (pgm->h.proc == data_list_trns_proc)
        {
          struct dls_var_spec *iter;
 
-         for (iter = pgm->spec; iter; iter = iter->next)
+         for (iter = pgm->first; iter; iter = iter->next)
            if (iter->v == v)
              {
                *spec = iter->input;
@@ -1582,7 +1697,7 @@ parse_num_or_var (struct rpd_num_or_var *value, const char *message)
          return 0;
        }
     }
-  else if (lex_integer_p ())
+  else if (lex_is_integer ())
     {
       value->num = lex_integer ();
       
@@ -1600,11 +1715,13 @@ parse_num_or_var (struct rpd_num_or_var *value, const char *message)
   return 1;
 }
 
-/* Parses data specifications for repeating data groups.  Taken from
-   parse_fixed().  Returns nonzero only if successful.  */
+/* Parses data specifications for repeating data groups, adding
+   them to the linked list with head FIRST and tail LAST.
+   Returns nonzero only if successful.  */
 static int
-parse_repeating_data (void)
+parse_repeating_data (struct dls_var_spec **first, struct dls_var_spec **last)
 {
+  struct fixed_parsing_state fx;
   int i;
 
   fx.recno = 0;
@@ -1612,44 +1729,35 @@ parse_repeating_data (void)
 
   while (token != '.')
     {
-      fx.spec.rec = fx.recno;
-
-      if (!parse_DATA_LIST_vars (&fx.name, &fx.nname, PV_NONE))
+      if (!parse_DATA_LIST_vars (&fx.name, &fx.name_cnt, PV_NONE))
        return 0;
 
-      if (token == T_NUM)
+      if (lex_is_number ())
        {
-         if (!fixed_parse_compatible ())
+         if (!fixed_parse_compatible (&fx, first, last))
            goto fail;
        }
       else if (token == '(')
        {
-         fx.level = 0;
-         fx.cname = 0;
-         if (!fixed_parse_fortran ())
+         if (!fixed_parse_fortran (&fx, first, last))
            goto fail;
        }
       else
        {
          msg (SE, _("SPSS-like or FORTRAN-like format "
-              "specification expected after variable names."));
+                     "specification expected after variable names."));
          goto fail;
        }
 
-      for (i = 0; i < fx.nname; i++)
+      for (i = 0; i < fx.name_cnt; i++)
        free (fx.name[i]);
       free (fx.name);
     }
-  if (token != '.')
-    {
-      lex_error (_("expecting end of command"));
-      return 0;
-    }
   
   return 1;
 
-fail:
-  for (i = 0; i < fx.nname; i++)
+ fail:
+  for (i = 0; i < fx.name_cnt; i++)
     free (fx.name[i]);
   free (fx.name);
   return 0;
@@ -1662,52 +1770,49 @@ fail:
 static int
 realize_value (struct rpd_num_or_var *n, struct ccase *c)
 {
-  if (n->num > 0)
-    return n->num;
-  
-  assert (n->num == 0);
   if (n->var != NULL)
     {
-      double v = c->data[n->var->fv].f;
-
-      if (v == SYSMIS || v <= INT_MIN || v >= INT_MAX)
-       return -1;
-      else
-       return v;
+      double v = case_num (c, n->var->fv);
+      return v != SYSMIS && v >= INT_MIN && v <= INT_MAX ? v : -1;
     }
   else
-    return 0;
+    return n->num;
 }
 
-/* Parses one record of repeated data and outputs corresponding cases.
-   Repeating data is present in line LINE having length LEN.
-   Repeating data begins in column BEG and continues through column
-   END inclusive (1-based columns); occurrences are offset OFS columns
-   from each other.  C is the case that will be filled in; T is the
-   REPEATING DATA transformation.  The record ID will be verified if
-   COMPARE_ID is nonzero; if it is zero, then the record ID is
-   initialized to the ID present in the case (assuming that ID
-   location was specified by the user).  Returns number of occurrences
-   parsed up to the specified maximum of MAX_OCCURS. */
+/* Parameter record passed to rpd_parse_record(). */
+struct rpd_parse_info 
+  {
+    struct repeating_data_trns *trns;  /* REPEATING DATA transformation. */
+    const char *line;   /* Line being parsed. */
+    size_t len;         /* Line length. */
+    int beg, end;       /* First and last column of first occurrence. */
+    int ofs;            /* Column offset between repeated occurrences. */
+    struct ccase *c;    /* Case to fill in. */
+    int verify_id;      /* Zero to initialize ID, nonzero to verify it. */
+    int max_occurs;     /* Max number of occurrences to parse. */
+  };
+
+/* Parses one record of repeated data and outputs corresponding
+   cases.  Returns number of occurrences parsed up to the
+   maximum specified in INFO. */
 static int
-rpd_parse_record (int beg, int end, int ofs, struct ccase *c,
-                 struct repeating_data_trns *t,
-                 char *line, int len, int compare_id, int max_occurs)
+rpd_parse_record (const struct rpd_parse_info *info)
 {
+  struct repeating_data_trns *t = info->trns;
+  int cur = info->beg;
   int occurrences;
-  int cur = beg;
 
   /* Handle record ID values. */
   if (t->id_beg != 0)
     {
-      static union value comparator;
-      union value v;
+      union value id_temp[MAX_ELEMS_PER_VALUE];
       
+      /* Parse record ID into V. */
       {
        struct data_in di;
 
-       data_in_finite_line (&di, line, len, t->id_beg, t->id_end);
-       di.v = &v;
+       data_in_finite_line (&di, info->line, info->len, t->id_beg, t->id_end);
+       di.v = info->verify_id ? id_temp : t->id_value;
        di.flags = 0;
        di.f1 = t->id_beg;
        di.format = t->id_spec;
@@ -1716,25 +1821,21 @@ rpd_parse_record (int beg, int end, int ofs, struct ccase *c,
          return 0;
       }
 
-      if (compare_id == 0)
-       comparator = v;
-      else if ((t->id_var->type == NUMERIC && comparator.f != v.f)
-              || (t->id_var->type == ALPHA
-                  && strncmp (comparator.s, v.s, t->id_var->width)))
+      if (info->verify_id
+          && compare_values (id_temp, t->id_value, t->id_var->width) != 0)
        {
-         char comp_str [64];
-         char v_str [64];
+         char expected_str [MAX_FORMATTED_LEN + 1];
+         char actual_str [MAX_FORMATTED_LEN + 1];
 
-         if (!data_out (comp_str, &t->id_var->print, &comparator))
-           comp_str[0] = 0;
-         if (!data_out (v_str, &t->id_var->print, &v))
-           v_str[0] = 0;
-         
-         comp_str[t->id_var->print.w] = v_str[t->id_var->print.w] = 0;
+         data_out (expected_str, &t->id_var->print, t->id_value);
+          expected_str[t->id_var->print.w] = '\0';
+
+         data_out (actual_str, &t->id_var->print, id_temp);
+          actual_str[t->id_var->print.w] = '\0';
            
          tmsg (SE, RPD_ERR, 
-               _("Mismatched case ID (%s).  Expected value was %s."),
-               v_str, comp_str);
+               _("Encountered mismatched record ID \"%s\" expecting \"%s\"."),
+               actual_str, expected_str);
 
          return 0;
        }
@@ -1746,35 +1847,35 @@ rpd_parse_record (int beg, int end, int ofs, struct ccase *c,
   {
     int warned = 0;
 
-    for (occurrences = 0; occurrences < max_occurs; )
+    for (occurrences = 0; occurrences < info->max_occurs; )
       {
-       if (cur + ofs > end + 1)
+       if (cur + info->ofs > info->end + 1)
          break;
        occurrences++;
 
        {
-         struct dls_var_spec *var_spec = t->spec;
+         struct dls_var_spec *var_spec = t->first;
        
          for (; var_spec; var_spec = var_spec->next)
            {
              int fc = var_spec->fc - 1 + cur;
              int lc = var_spec->lc - 1 + cur;
 
-             if (fc > len && !warned && var_spec->input.type != FMT_A)
+             if (fc > info->len && !warned && var_spec->input.type != FMT_A)
                {
                  warned = 1;
 
                  tmsg (SW, RPD_ERR,
                        _("Variable %s starting in column %d extends "
                          "beyond physical record length of %d."),
-                       var_spec->v->name, fc, len);
+                       var_spec->v->name, fc, info->len);
                }
              
              {
                struct data_in di;
 
-               data_in_finite_line (&di, line, len, fc, lc);
-               di.v = &c->data[var_spec->fv];
+               data_in_finite_line (&di, info->line, info->len, fc, lc);
+               di.v = case_data_rw (info->c, var_spec->fv);
                di.flags = 0;
                di.f1 = fc + 1;
                di.format = var_spec->input;
@@ -1785,9 +1886,9 @@ rpd_parse_record (int beg, int end, int ofs, struct ccase *c,
            }
        }
 
-       cur += ofs;
+       cur += info->ofs;
 
-       if (!write_case ())
+       if (!t->write_case (t->wc_data))
          return 0;
       }
   }
@@ -1795,140 +1896,184 @@ rpd_parse_record (int beg, int end, int ofs, struct ccase *c,
   return occurrences;
 }
 
-/* Analogous to read_one_case; reads one set of repetitions of the
-   elements in the REPEATING DATA structure.  Returns -1 on success,
-   -2 on end of file or on failure. */
-static int
-read_one_set_of_repetitions (struct trns_header *trns, struct ccase *c)
+/* Reads one set of repetitions of the elements in the REPEATING
+   DATA structure.  Returns -1 on success, -2 on end of file or
+   on failure. */
+int
+repeating_data_trns_proc (struct trns_header *trns, struct ccase *c,
+                          int case_num UNUSED)
 {
-  dfm_push (dlsp->handle);
-  
-  {
-    struct repeating_data_trns *t = (struct repeating_data_trns *) trns;
+  struct repeating_data_trns *t = (struct repeating_data_trns *) trns;
     
-    char *line;                /* Current record. */
-    int len;           /* Length of current record. */
+  struct fixed_string line;       /* Current record. */
 
-    int starts_beg;    /* Starting column. */
-    int starts_end;    /* Ending column. */
-    int occurs;                /* Number of repetitions. */
-    int length;                /* Length of each occurrence. */
-    int cont_beg;      /* Starting column for continuation lines. */
-    int cont_end;      /* Ending column for continuation lines. */
+  int starts_beg;      /* Starting column. */
+  int starts_end;      /* Ending column. */
+  int occurs;          /* Number of repetitions. */
+  int length;          /* Length of each occurrence. */
+  int cont_beg;         /* Starting column for continuation lines. */
+  int cont_end;         /* Ending column for continuation lines. */
 
-    int occurs_left;   /* Number of occurrences remaining. */
+  int occurs_left;     /* Number of occurrences remaining. */
 
-    int code;          /* Return value from rpd_parse_record(). */
+  int code;            /* Return value from rpd_parse_record(). */
     
-    int skip_first_record = 0;
+  int skip_first_record = 0;
     
-    /* Read the current record. */
-    dfm_bkwd_record (dlsp->handle, 1);
-    line = dfm_get_record (dlsp->handle, &len);
-    if (line == NULL)
-      return -2;
-    dfm_fwd_record (dlsp->handle);
-
-    /* Calculate occurs, length. */
-    occurs_left = occurs = realize_value (&t->occurs, c);
-    if (occurs <= 0)
-      {
-       tmsg (SE, RPD_ERR, _("Invalid value %d for OCCURS."), occurs);
-       return -3;
-      }
-    starts_beg = realize_value (&t->starts_beg, c);
-    if (starts_beg <= 0)
-      {
-       tmsg (SE, RPD_ERR, _("Beginning column for STARTS (%d) must be "
-                            "at least 1."),
-             starts_beg);
-       return -3;
-      }
-    starts_end = realize_value (&t->starts_end, c);
-    if (starts_end < starts_beg)
-      {
-       tmsg (SE, RPD_ERR, _("Ending column for STARTS (%d) is less than "
-                            "beginning column (%d)."),
-             starts_end, starts_beg);
-       skip_first_record = 1;
-      }
-    length = realize_value (&t->length, c);
-    if (length < 0)
-      {
-       tmsg (SE, RPD_ERR, _("Invalid value %d for LENGTH."), length);
-       length = 1;
-       occurs = occurs_left = 1;
-      }
-    cont_beg = realize_value (&t->cont_beg, c);
-    if (cont_beg < 0)
-      {
-       tmsg (SE, RPD_ERR, _("Beginning column for CONTINUED (%d) must be "
-                            "at least 1."),
-             cont_beg);
-       return -2;
-      }
-    cont_end = realize_value (&t->cont_end, c);
-    if (cont_end < cont_beg)
-      {
-       tmsg (SE, RPD_ERR, _("Ending column for CONTINUED (%d) is less than "
-                            "beginning column (%d)."),
-             cont_end, cont_beg);
-       return -2;
-      }
+  dfm_push (t->reader);
+  
+  /* Read the current record. */
+  dfm_reread_record (t->reader, 1);
+  dfm_expand_tabs (t->reader);
+  if (dfm_eof (t->reader))
+    return -2;
+  dfm_get_record (t->reader, &line);
+  dfm_forward_record (t->reader);
 
-    /* Parse the first record. */
-    if (!skip_first_record)
-      {
-       code = rpd_parse_record (starts_beg, starts_end, length, c, t, line,
-                                len, 0, occurs_left);
-       if (!code)
-         return -2;
-      }
-    else if (cont_beg == 0)
+  /* Calculate occurs, length. */
+  occurs_left = occurs = realize_value (&t->occurs, c);
+  if (occurs <= 0)
+    {
+      tmsg (SE, RPD_ERR, _("Invalid value %d for OCCURS."), occurs);
+      return -3;
+    }
+  starts_beg = realize_value (&t->starts_beg, c);
+  if (starts_beg <= 0)
+    {
+      tmsg (SE, RPD_ERR, _("Beginning column for STARTS (%d) must be "
+                           "at least 1."),
+            starts_beg);
       return -3;
+    }
+  starts_end = realize_value (&t->starts_end, c);
+  if (starts_end < starts_beg)
+    {
+      tmsg (SE, RPD_ERR, _("Ending column for STARTS (%d) is less than "
+                           "beginning column (%d)."),
+            starts_end, starts_beg);
+      skip_first_record = 1;
+    }
+  length = realize_value (&t->length, c);
+  if (length < 0)
+    {
+      tmsg (SE, RPD_ERR, _("Invalid value %d for LENGTH."), length);
+      length = 1;
+      occurs = occurs_left = 1;
+    }
+  cont_beg = realize_value (&t->cont_beg, c);
+  if (cont_beg < 0)
+    {
+      tmsg (SE, RPD_ERR, _("Beginning column for CONTINUED (%d) must be "
+                           "at least 1."),
+            cont_beg);
+      return -2;
+    }
+  cont_end = realize_value (&t->cont_end, c);
+  if (cont_end < cont_beg)
+    {
+      tmsg (SE, RPD_ERR, _("Ending column for CONTINUED (%d) is less than "
+                           "beginning column (%d)."),
+            cont_end, cont_beg);
+      return -2;
+    }
 
-    /* Make sure, if some occurrences are left, that we have
-       continuation records. */
-    occurs_left -= code;
-    if (occurs_left != 0 && cont_beg == 0)
-      {
-       tmsg (SE, RPD_ERR,
-             _("Number of repetitions specified on OCCURS (%d) "
-               "exceed number of repetitions available in "
-               "space on STARTS (%d), and CONTINUED not specified."),
-             occurs, code);
-       return -2;
-      }
+  /* Parse the first record. */
+  if (!skip_first_record)
+    {
+      struct rpd_parse_info info;
+      info.trns = t;
+      info.line = ls_c_str (&line);
+      info.len = ls_length (&line);
+      info.beg = starts_beg;
+      info.end = starts_end;
+      info.ofs = length;
+      info.c = c;
+      info.verify_id = 0;
+      info.max_occurs = occurs_left;
+      code = rpd_parse_record (&info);
+      if (!code)
+        return -2;
+      occurs_left -= code;
+    }
+  else if (cont_beg == 0)
+    return -3;
 
-    /* Go on to additional records. */
-    while (occurs_left != 0)
-      {
-       assert (occurs_left >= 0);
+  /* Make sure, if some occurrences are left, that we have
+     continuation records. */
+  if (occurs_left > 0 && cont_beg == 0)
+    {
+      tmsg (SE, RPD_ERR,
+            _("Number of repetitions specified on OCCURS (%d) "
+              "exceed number of repetitions available in "
+              "space on STARTS (%d), and CONTINUED not specified."),
+            occurs, (starts_end - starts_beg + 1) / length);
+      return -2;
+    }
 
-       /* Read in another record. */
-       line = dfm_get_record (dlsp->handle, &len);
-       if (line == NULL)
-         {
-           tmsg (SE, RPD_ERR,
-                 _("Unexpected end of file with %d repetitions "
-                   "remaining out of %d."),
-                 occurs_left, occurs);
-           return -2;
-         }
-       dfm_fwd_record (dlsp->handle);
+  /* Go on to additional records. */
+  while (occurs_left != 0)
+    {
+      struct rpd_parse_info info;
 
-       /* Parse this record. */
-       code = rpd_parse_record (cont_beg, cont_end, length, c, t, line,
-                                len, 1, occurs_left);
-       if (!code)
-         return -2;
-       occurs_left -= code;
-      }
-  }
+      assert (occurs_left >= 0);
+
+      /* Read in another record. */
+      if (dfm_eof (t->reader))
+        {
+          tmsg (SE, RPD_ERR,
+                _("Unexpected end of file with %d repetitions "
+                  "remaining out of %d."),
+                occurs_left, occurs);
+          return -2;
+        }
+      dfm_expand_tabs (t->reader);
+      dfm_get_record (t->reader, &line);
+      dfm_forward_record (t->reader);
+
+      /* Parse this record. */
+      info.trns = t;
+      info.line = ls_c_str (&line);
+      info.len = ls_length (&line);
+      info.beg = cont_beg;
+      info.end = cont_end;
+      info.ofs = length;
+      info.c = c;
+      info.verify_id = 1;
+      info.max_occurs = occurs_left;
+      code = rpd_parse_record (&info);;
+      if (!code)
+        return -2;
+      occurs_left -= code;
+    }
     
-  dfm_pop (dlsp->handle);
+  dfm_pop (t->reader);
 
   /* FIXME: This is a kluge until we've implemented multiplexing of
      transformations. */
   return -3;
 }
+
+/* Frees a REPEATING DATA transformation. */
+void
+repeating_data_trns_free (struct trns_header *rpd_) 
+{
+  struct repeating_data_trns *rpd = (struct repeating_data_trns *) rpd_;
+
+  destroy_dls_var_spec (rpd->first);
+  dfm_close_reader (rpd->reader);
+  free (rpd->id_value);
+}
+
+/* Lets repeating_data_trns_proc() know how to write the cases
+   that it composes.  Not elegant. */
+void
+repeating_data_set_write_case (struct trns_header *trns,
+                               write_case_func *write_case,
+                               write_case_data wc_data) 
+{
+  struct repeating_data_trns *t = (struct repeating_data_trns *) trns;
+
+  assert (trns->proc == repeating_data_trns_proc);
+  t->write_case = write_case;
+  t->wc_data = wc_data;
+}