Bug #21111. Reviewed by John Darrington.
[pspp-builds.git] / src / language / data-io / data-list.c
index e3324cf20f354f575315da1fbfa25b9b2d11082b..4e2f083bee9077f2f69c990624fc6694b8ed0749 100644 (file)
@@ -1,80 +1,85 @@
-/* PSPP - computes sample statistics.
+/* PSPP - a program for statistical analysis.
    Copyright (C) 1997-9, 2000, 2006 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
-   modify it under the terms of the GNU General Public License as
-   published by the Free Software Foundation; either version 2 of the
-   License, or (at your option) any later version.
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
 
-   This program is distributed in the hope that it will be useful, but
-   WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-   General Public License for more details.
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
 
    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., 51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301, USA. */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
 
 #include <config.h>
-#include <language/data-io/data-list.h>
-#include <libpspp/message.h>
+
 #include <ctype.h>
 #include <float.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <ctype.h>
 #include <float.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <libpspp/alloc.h>
+
 #include <data/case.h>
 #include <data/case.h>
-#include <language/command.h>
-#include <libpspp/compiler.h>
 #include <data/data-in.h>
 #include <data/data-in.h>
-#include <libpspp/debug-print.h>
-#include <language/data-io/data-reader.h>
+#include <data/casereader.h>
+#include <data/casereader-provider.h>
 #include <data/dictionary.h>
 #include <data/dictionary.h>
-#include <libpspp/message.h>
-#include <language/data-io/file-handle.h>
 #include <data/format.h>
 #include <data/format.h>
+#include <data/procedure.h>
+#include <data/settings.h>
+#include <data/transformations.h>
+#include <data/variable.h>
+#include <language/command.h>
+#include <language/data-io/data-reader.h>
+#include <language/data-io/file-handle.h>
+#include <language/data-io/inpt-pgm.h>
+#include <language/data-io/placement-parser.h>
+#include <language/lexer/format-parser.h>
 #include <language/lexer/lexer.h>
 #include <language/lexer/lexer.h>
+#include <language/lexer/variable-parser.h>
+#include <libpspp/alloc.h>
+#include <libpspp/assertion.h>
+#include <libpspp/compiler.h>
+#include <libpspp/ll.h>
+#include <libpspp/message.h>
 #include <libpspp/misc.h>
 #include <libpspp/misc.h>
-#include <data/settings.h>
+#include <libpspp/pool.h>
 #include <libpspp/str.h>
 #include <output/table.h>
 #include <libpspp/str.h>
 #include <output/table.h>
-#include <data/variable.h>
-#include <procedure.h>
+
+#include "size_max.h"
+#include "xsize.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 \f
 /* Utility function. */
 
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 \f
 /* Utility function. */
 
-/* FIXME: Either REPEATING DATA must be the last transformation, or we
-   must multiplex the transformations that follow (i.e., perform them
-   for every case that we produce from a repetition instance).
-   Currently we do neither.  We should do one or the other. */
-   
 /* Describes how to parse one variable. */
 struct dls_var_spec
   {
 /* Describes how to parse one variable. */
 struct dls_var_spec
   {
-    struct dls_var_spec *next;  /* Next specification in list. */
+    struct ll ll;               /* List element. */
 
 
-    /* Both free and fixed formats. */
+    /* All parsers. */
     struct fmt_spec input;     /* Input format of this field. */
     struct fmt_spec input;     /* Input format of this field. */
-    struct variable *v;                /* Associated variable.  Used only in
-                                  parsing.  Not safe later. */
     int fv;                    /* First value in case. */
     int fv;                    /* First value in case. */
+    char name[LONG_NAME_LEN + 1]; /* Var name for error messages and tables. */
 
     /* Fixed format only. */
 
     /* 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. */
+    int record;                        /* Record number (1-based). */
+    int first_column;           /* Column numbers in record. */
   };
 
   };
 
+static struct dls_var_spec *
+ll_to_dls_var_spec (struct ll *ll)
+{
+  return ll_data (ll, struct dls_var_spec, ll);
+}
+
 /* Constants for DATA LIST type. */
 /* Constants for DATA LIST type. */
-/* Must match table in cmd_data_list(). */
-enum
+enum dls_type
   {
     DLS_FIXED,
     DLS_FREE,
   {
     DLS_FIXED,
     DLS_FREE,
@@ -84,111 +89,116 @@ enum
 /* DATA LIST private data structure. */
 struct data_list_pgm
   {
 /* DATA LIST private data structure. */
 struct data_list_pgm
   {
-    struct dls_var_spec *first, *last; /* Variable parsing specifications. */
+    struct pool *pool;          /* Used for all DATA LIST storage. */
+    struct ll_list specs;       /* List of dls_var_specs. */
     struct dfm_reader *reader;  /* Data file reader. */
     struct dfm_reader *reader;  /* Data file reader. */
-
-    int type;                  /* A DLS_* constant. */
+    enum dls_type type;                /* Type of DATA LIST construct. */
     struct variable *end;      /* Variable specified on END subcommand. */
     struct variable *end;      /* Variable specified on END subcommand. */
-    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. */
+    int record_cnt;             /* Number of records. */
+    struct string delims;       /* Field delimiters. */
+    int skip_records;           /* Records to skip before first case. */
+    size_t value_cnt;           /* Number of `union value's in case. */
   };
 
   };
 
-static const struct case_source_class data_list_source_class;
+static const struct casereader_class data_list_casereader_class;
 
 
-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 bool parse_fixed (struct lexer *, struct dictionary *dict,
+                        struct pool *tmp_pool, struct data_list_pgm *);
+static bool parse_free (struct lexer *, struct dictionary *dict,
+                       struct pool *tmp_pool, struct data_list_pgm *);
+static void dump_fixed_table (const struct ll_list *,
+                              const struct file_handle *, int record_cnt);
 static void dump_free_table (const struct data_list_pgm *,
                              const struct file_handle *);
 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;
 
 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: "
-
 int
 int
-cmd_data_list (void)
+cmd_data_list (struct lexer *lexer, struct dataset *ds)
 {
 {
+  struct dictionary *dict;
   struct data_list_pgm *dls;
   int table = -1;                /* Print table if nonzero, -1=undecided. */
   struct file_handle *fh = fh_inline_file ();
   struct data_list_pgm *dls;
   int table = -1;                /* Print table if nonzero, -1=undecided. */
   struct file_handle *fh = fh_inline_file ();
+  struct pool *tmp_pool;
+  bool ok;
 
 
-  if (!case_source_is_complex (vfm_source))
-    discard_variables ();
+  dict = in_input_program () ? dataset_dict (ds) : dict_create ();
 
 
-  dls = xmalloc (sizeof *dls);
+  dls = pool_create_container (struct data_list_pgm, pool);
+  ll_init (&dls->specs);
   dls->reader = NULL;
   dls->type = -1;
   dls->end = NULL;
   dls->reader = NULL;
   dls->type = -1;
   dls->end = NULL;
-  dls->rec_cnt = 0;
-  dls->delims = NULL;
-  dls->delim_cnt = 0;
-  dls->first = dls->last = NULL;
+  dls->record_cnt = 0;
+  dls->skip_records = 0;
+  ds_init_empty (&dls->delims);
+  ds_register_pool (&dls->delims, dls->pool);
+
+  tmp_pool = pool_create_subpool (dls->pool);
 
 
-  while (token != '/')
+  while (lex_token (lexer) != '/')
     {
     {
-      if (lex_match_id ("FILE"))
+      if (lex_match_id (lexer, "FILE"))
        {
        {
-         lex_match ('=');
-         fh = fh_parse (FH_REF_FILE | FH_REF_INLINE);
+         lex_match (lexer, '=');
+         fh = fh_parse (lexer, FH_REF_FILE | FH_REF_INLINE);
          if (fh == NULL)
            goto error;
          if (fh == NULL)
            goto error;
-         if (case_source_is_class (vfm_source, &file_type_source_class)
-              && fh != fh_get_default_handle ())
-           {
-             msg (SE, _("DATA LIST must use the same file "
-                        "as the enclosing FILE TYPE."));
-             goto error;
-           }
        }
        }
-      else if (lex_match_id ("RECORDS"))
+      else if (lex_match_id (lexer, "RECORDS"))
        {
        {
-         lex_match ('=');
-         lex_match ('(');
-         if (!lex_force_int ())
+         lex_match (lexer, '=');
+         lex_match (lexer, '(');
+         if (!lex_force_int (lexer))
            goto error;
            goto error;
-         dls->rec_cnt = lex_integer ();
-         lex_get ();
-         lex_match (')');
+         dls->record_cnt = lex_integer (lexer);
+         lex_get (lexer);
+         lex_match (lexer, ')');
        }
        }
-      else if (lex_match_id ("END"))
+      else if (lex_match_id (lexer, "SKIP"))
+       {
+         lex_match (lexer, '=');
+         if (!lex_force_int (lexer))
+           goto error;
+         dls->skip_records = lex_integer (lexer);
+         lex_get (lexer);
+       }
+      else if (lex_match_id (lexer, "END"))
        {
          if (dls->end)
            {
              msg (SE, _("The END subcommand may only be specified once."));
              goto error;
            }
        {
          if (dls->end)
            {
              msg (SE, _("The END subcommand may only be specified once."));
              goto error;
            }
-         
-         lex_match ('=');
-         if (!lex_force_id ())
+
+         lex_match (lexer, '=');
+         if (!lex_force_id (lexer))
            goto error;
            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 ();
+         dls->end = dict_lookup_var (dict, lex_tokid (lexer));
+         if (!dls->end)
+            dls->end = dict_create_var_assert (dict, lex_tokid (lexer), 0);
+         lex_get (lexer);
        }
        }
-      else if (token == T_ID)
+      else if (lex_token (lexer) == T_ID)
        {
        {
-          if (lex_match_id ("NOTABLE"))
+          if (lex_match_id (lexer, "NOTABLE"))
             table = 0;
             table = 0;
-          else if (lex_match_id ("TABLE"))
+          else if (lex_match_id (lexer, "TABLE"))
             table = 1;
             table = 1;
-          else 
+          else
             {
               int type;
             {
               int type;
-              if (lex_match_id ("FIXED"))
+              if (lex_match_id (lexer, "FIXED"))
                 type = DLS_FIXED;
                 type = DLS_FIXED;
-              else if (lex_match_id ("FREE"))
+              else if (lex_match_id (lexer, "FREE"))
                 type = DLS_FREE;
                 type = DLS_FREE;
-              else if (lex_match_id ("LIST"))
+              else if (lex_match_id (lexer, "LIST"))
                 type = DLS_LIST;
                 type = DLS_LIST;
-              else 
+              else
                 {
                 {
-                  lex_error (NULL);
+                  lex_error (lexer, NULL);
                   goto error;
                 }
 
                   goto error;
                 }
 
@@ -201,77 +211,81 @@ cmd_data_list (void)
              dls->type = type;
 
               if ((dls->type == DLS_FREE || dls->type == DLS_LIST)
              dls->type = type;
 
               if ((dls->type == DLS_FREE || dls->type == DLS_LIST)
-                  && lex_match ('(')) 
+                  && lex_match (lexer, '('))
                 {
                 {
-                  while (!lex_match (')'))
+                  while (!lex_match (lexer, ')'))
                     {
                       int delim;
 
                     {
                       int delim;
 
-                      if (lex_match_id ("TAB"))
+                      if (lex_match_id (lexer, "TAB"))
                         delim = '\t';
                         delim = '\t';
-                      else if (token == T_STRING && tokstr.length == 1)
+                      else if (lex_token (lexer) == T_STRING && ds_length (lex_tokstr (lexer)) == 1)
                        {
                        {
-                         delim = tokstr.string[0];
-                         lex_get();
+                         delim = ds_first (lex_tokstr (lexer));
+                         lex_get (lexer);
                        }
                        }
-                      else 
+                      else
                         {
                         {
-                          lex_error (NULL);
+                          lex_error (lexer, NULL);
                           goto error;
                         }
 
                           goto error;
                         }
 
-                      dls->delims = xrealloc (dls->delims, dls->delim_cnt + 1);
-                      dls->delims[dls->delim_cnt++] = delim;
+                      ds_put_char (&dls->delims, delim);
 
 
-                      lex_match (',');
+                      lex_match (lexer, ',');
                     }
                 }
             }
         }
       else
        {
                     }
                 }
             }
         }
       else
        {
-         lex_error (NULL);
+         lex_error (lexer, NULL);
          goto error;
        }
     }
 
          goto error;
        }
     }
 
-  dls->case_size = dict_get_case_size (default_dict);
   fh_set_default_handle (fh);
 
   if (dls->type == -1)
     dls->type = DLS_FIXED;
 
   if (table == -1)
   fh_set_default_handle (fh);
 
   if (dls->type == -1)
     dls->type = DLS_FIXED;
 
   if (table == -1)
-    {
-      if (dls->type == DLS_FREE)
-       table = 0;
-      else
-       table = 1;
-    }
+    table = dls->type != DLS_FREE;
 
 
-  if (dls->type == DLS_FIXED)
-    {
-      if (!parse_fixed (dls))
-       goto error;
-      if (table)
-       dump_fixed_table (dls->first, fh, dls->rec_cnt);
-    }
-  else
+  ok = (dls->type == DLS_FIXED ? parse_fixed : parse_free) (lexer, dict, tmp_pool, dls);
+  if (!ok)
+    goto error;
+
+  if (lex_end_of_command (lexer) != CMD_SUCCESS)
+    goto error;
+
+  if (table)
     {
     {
-      if (!parse_free (&dls->first, &dls->last))
-       goto error;
-      if (table)
+      if (dls->type == DLS_FIXED)
+       dump_fixed_table (&dls->specs, fh, dls->record_cnt);
+      else
        dump_free_table (dls, fh);
     }
 
        dump_free_table (dls, fh);
     }
 
-  dls->reader = dfm_open_reader (fh);
+  dls->reader = dfm_open_reader (fh, lexer);
   if (dls->reader == NULL)
     goto error;
 
   if (dls->reader == NULL)
     goto error;
 
-  if (vfm_source != NULL)
-    add_transformation (data_list_trns_proc, data_list_trns_free, dls);
-  else 
-    vfm_source = create_case_source (&data_list_source_class, dls);
+  dls->value_cnt = dict_get_next_value_idx (dict);
+
+  if (in_input_program ())
+    add_transformation (ds, data_list_trns_proc, data_list_trns_free, dls);
+  else
+    {
+      struct casereader *reader;
+      reader = casereader_create_sequential (NULL,
+                                             dict_get_next_value_idx (dict),
+                                             -1, &data_list_casereader_class,
+                                             dls);
+      proc_set_active_file (ds, reader, dict);
+    }
+
+  pool_destroy (tmp_pool);
 
   return CMD_SUCCESS;
 
 
   return CMD_SUCCESS;
 
@@ -279,603 +293,224 @@ cmd_data_list (void)
   data_list_trns_free (dls);
   return CMD_CASCADING_FAILURE;
 }
   data_list_trns_free (dls);
   return CMD_CASCADING_FAILURE;
 }
-
-/* Adds SPEC to the linked list with head at FIRST and tail at
-   LAST. */
-static void
-append_var_spec (struct dls_var_spec **first, struct dls_var_spec **last,
-                 struct dls_var_spec *spec)
-{
-  spec->next = NULL;
-
-  if (*first == NULL)
-    *first = spec;
-  else 
-    (*last)->next = spec;
-  *last = spec;
-}
 \f
 /* Fixed-format parsing. */
 
 \f
 /* Fixed-format parsing. */
 
-/* Used for chaining together fortran-like format specifiers. */
-struct fmt_list
-  {
-    struct fmt_list *next;
-    int count;
-    struct fmt_spec f;
-    struct fmt_list *down;
-  };
-
-/* State of parsing DATA LIST. */
-struct fixed_parsing_state
-  {
-    char **name;               /* Variable names. */
-    size_t 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. */
-  };
-
-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,
 /* Parses all the variable specifications for DATA LIST FIXED,
-   storing them into DLS.  Returns nonzero if successful. */
-static int
-parse_fixed (struct data_list_pgm *dls)
-{
-  struct fixed_parsing_state fx;
-  size_t i;
-
-  fx.recno = 0;
-  fx.sc = 1;
-
-  while (token != '.')
-    {
-      while (lex_match ('/'))
-       {
-         fx.recno++;
-         if (lex_is_integer ())
-           {
-             if (lex_integer () < fx.recno)
-               {
-                 msg (SE, _("The record number specified, %ld, is "
-                            "before the previous record, %d.  Data "
-                            "fields must be listed in order of "
-                            "increasing record number."),
-                      lex_integer (), fx.recno - 1);
-                 return 0;
-               }
-             
-             fx.recno = lex_integer ();
-             lex_get ();
-           }
-         fx.sc = 1;
-       }
-
-      if (!parse_DATA_LIST_vars (&fx.name, &fx.name_cnt, PV_NONE))
-       return 0;
-
-      if (lex_is_number ())
-       {
-         if (!fixed_parse_compatible (&fx, &dls->first, &dls->last))
-           goto fail;
-       }
-      else if (token == '(')
-       {
-         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."));
-         goto fail;
-       }
-
-      for (i = 0; i < fx.name_cnt; i++)
-       free (fx.name[i]);
-      free (fx.name);
-    }
-  if (dls->first == NULL) 
-    {
-      msg (SE, _("At least one variable must be specified."));
-      return 0;
-    }
-  if (dls->rec_cnt && dls->last->rec > dls->rec_cnt)
-    {
-      msg (SE, _("Variables are specified on records that "
-                "should not exist according to RECORDS subcommand."));
-      return 0;
-    }
-  else if (!dls->rec_cnt)
-    dls->rec_cnt = dls->last->rec;
-  return lex_end_of_command () == CMD_SUCCESS;
-
-fail:
-  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 (struct fixed_parsing_state *fx,
-                        struct dls_var_spec **first, struct dls_var_spec **last)
-{
-  struct fmt_spec input;
-  int fc, lc;
-  int width;
-  int i;
-
-  /* First column. */
-  if (!lex_force_int ())
-    return 0;
-  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;
-      lc = lex_integer ();
-      if (lc < 1)
-       {
-         msg (SE, _("Column positions for fields must be positive."));
-         return 0;
-       }
-      else if (lc < fc)
-       {
-         msg (SE, _("The ending column for a field must be "
-                    "greater than the starting column."));
-         return 0;
-       }
-      
-      lex_get ();
-    }
-  else
-    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;
-    }
-
-  /* Format specifier. */
-  if (lex_match ('('))
-    {
-      struct fmt_desc *fdp;
-
-      if (token == T_ID)
-       {
-         const char *cp;
-
-         input.type = parse_format_specifier_name (&cp, 0);
-         if (input.type == -1)
-           return 0;
-         if (*cp)
-           {
-             msg (SE, _("A format specifier on this line "
-                        "has extra characters on the end."));
-             return 0;
-           }
-         
-         lex_get ();
-         lex_match (',');
-       }
-      else
-       input.type = FMT_F;
-
-      if (lex_is_integer ())
-       {
-         if (lex_integer () < 1)
-           {
-             msg (SE, _("The value for number of decimal places "
-                        "must be at least 1."));
-             return 0;
-           }
-         
-         input.d = lex_integer ();
-         lex_get ();
-       }
-      else
-       input.d = 0;
-
-      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 (input.d > 16)
-       input.d = 16;
-
-      if (!lex_force_match (')'))
-       return 0;
-    }
-  else
-    {
-      input.type = FMT_F;
-      input.d = 0;
-    }
-  if (!check_input_specifier (&input, 1))
-    return 0;
-
-  /* Start column for next specification. */
-  fx->sc = lc + 1;
-
-  /* Width of variables to create. */
-  if (input.type == FMT_A || input.type == FMT_AHEX) 
-    width = input.w;
-  else
-    width = 0;
-
-  /* Create variables and var specs. */
-  for (i = 0; i < fx->name_cnt; i++)
-    {
-      struct dls_var_spec *spec;
-      struct variable *v;
-
-      v = dict_create_var (default_dict, fx->name[i], width);
-      if (v != NULL)
-       {
-         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_assert (default_dict, fx->name[i]);
-         if (vfm_source == NULL)
-           {
-             msg (SE, _("%s is a duplicate variable name."), fx->name[i]);
-             return 0;
-           }
-         if ((width != 0) != (v->width != 0))
-           {
-             msg (SE, _("There is already a variable %s of a "
-                        "different type."),
-                  fx->name[i]);
-             return 0;
-           }
-         if (width != 0 && width != v->width)
-           {
-             msg (SE, _("There is already a string variable %s of a "
-                        "different width."), fx->name[i]);
-             return 0;
-           }
-       }
-
-      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 format list F and, if RECURSE is nonzero, all its
-   sublists. */
-static void
-destroy_fmt_list (struct fmt_list *f, int recurse)
+   storing them into DLS.  Uses TMP_POOL for data that is not
+   needed once parsing is complete.  Returns true only if
+   successful. */
+static bool
+parse_fixed (struct lexer *lexer, struct dictionary *dict,
+            struct pool *tmp_pool, struct data_list_pgm *dls)
 {
 {
-  struct fmt_list *next;
-
-  for (; f; f = next)
-    {
-      next = f->next;
-      if (recurse && f->f.type == FMT_DESCEND)
-       destroy_fmt_list (f->down, 1);
-      free (f);
-    }
-}
+  int last_nonempty_record;
+  int record = 0;
+  int column = 1;
+
+  while (lex_token (lexer) != '.')
+    {
+      char **names;
+      size_t name_cnt, name_idx;
+      struct fmt_spec *formats, *f;
+      size_t format_cnt;
+
+      /* Parse everything. */
+      if (!parse_record_placement (lexer, &record, &column)
+          || !parse_DATA_LIST_vars_pool (lexer, tmp_pool,
+                                        &names, &name_cnt, PV_NONE)
+          || !parse_var_placements (lexer, tmp_pool, name_cnt, true,
+                                    &formats, &format_cnt))
+        return false;
 
 
-/* Takes a hierarchically structured fmt_list F as constructed by
-   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 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;
-    else if (f->f.type == FMT_T)
-      fx->sc = f->f.w;
-    else if (f->f.type == FMT_NEWREC)
-      {
-       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 (fx, f->down, first, last, name_idx))
-             return 0;
-         }
-       else
-         {
-            struct dls_var_spec *spec;
+      /* Create variables and var specs. */
+      name_idx = 0;
+      for (f = formats; f < &formats[format_cnt]; f++)
+        if (!execute_placement_format (f, &record, &column))
+          {
+            char *name;
             int width;
             int width;
-           struct variable *v;
+            struct variable *v;
+            struct dls_var_spec *spec;
 
 
-            if (formats[f->f.type].cat & FCAT_STRING) 
-              width = f->f.w;
+            name = names[name_idx++];
+
+            /* Create variable. */
+            width = fmt_var_width (f);
+            v = dict_create_var (dict, name, width);
+            if (v != NULL)
+              {
+                /* Success. */
+                struct fmt_spec output = fmt_for_output_from_input (f);
+                var_set_both_formats (v, &output);
+              }
             else
             else
-              width = 0;
-           if (*name_idx >= fx->name_cnt)
-             {
-               msg (SE, _("The number of format "
-                          "specifications exceeds the given number of "
-                          "variable names."));
-               return 0;
-             }
-           
-           v = dict_create_var (default_dict, fx->name[(*name_idx)++], width);
-           if (!v)
-             {
-               msg (SE, _("%s is a duplicate variable name."), fx->name[i]);
-               return 0;
-             }
-           
-            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->sc += f->f.w;
-         }
-  return 1;
-}
-
-/* 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_internal (struct fixed_parsing_state *fx,
-                              struct dls_var_spec **first,
-                              struct dls_var_spec **last)
-{
-  struct fmt_list *head = NULL;
-  struct fmt_list *tail = NULL;
-
-  lex_force_match ('(');
-  while (token != ')')
+              {
+                /* Failure.
+                   This can be acceptable if we're in INPUT
+                   PROGRAM, but only if the existing variable has
+                   the same width as the one we would have
+                   created. */
+                if (!in_input_program ())
+                  {
+                    msg (SE, _("%s is a duplicate variable name."), name);
+                    return false;
+                  }
+
+                v = dict_lookup_var_assert (dict, name);
+                if ((width != 0) != (var_get_width (v) != 0))
+                  {
+                    msg (SE, _("There is already a variable %s of a "
+                               "different type."),
+                         name);
+                    return false;
+                  }
+                if (width != 0 && width != var_get_width (v))
+                  {
+                    msg (SE, _("There is already a string variable %s of a "
+                               "different width."), name);
+                    return false;
+                  }
+              }
+
+            /* Create specifier for parsing the variable. */
+            spec = pool_alloc (dls->pool, sizeof *spec);
+            spec->input = *f;
+            spec->fv = var_get_case_index (v);
+            spec->record = record;
+            spec->first_column = column;
+            strcpy (spec->name, var_get_name (v));
+            ll_push_tail (&dls->specs, &spec->ll);
+
+            column += f->w;
+          }
+      assert (name_idx == name_cnt);
+    }
+  if (ll_is_empty (&dls->specs))
     {
     {
-      /* 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 = new;
-      tail = new;
-
-      /* Parse count. */
-      if (lex_is_integer ())
-       {
-         new->count = lex_integer ();
-         lex_get ();
-       }
-      else
-       new->count = 1;
-
-      /* Parse format specifier. */
-      if (token == '(')
-       {
-         new->f.type = FMT_DESCEND;
-         new->down = fixed_parse_fortran_internal (fx, first, last);
-         if (new->down == NULL)
-           goto fail;
-       }
-      else if (lex_match ('/'))
-       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 (',');
+      msg (SE, _("At least one variable must be specified."));
+      return false;
     }
     }
-  lex_force_match (')');
-
-  return head;
-
-fail:
-  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)
+  last_nonempty_record = ll_to_dls_var_spec (ll_tail (&dls->specs))->record;
+  if (dls->record_cnt && last_nonempty_record > dls->record_cnt)
     {
     {
-      msg (SE, _("There aren't enough format specifications "
-                 "to match the number of variable names given."));
-      return 0; 
+      msg (SE, _("Variables are specified on records that "
+                "should not exist according to RECORDS subcommand."));
+      return false;
     }
     }
+  else if (!dls->record_cnt)
+    dls->record_cnt = last_nonempty_record;
 
 
-  return 1;
+  return true;
 }
 
 /* Displays a table giving information on fixed-format variable
    parsing on DATA LIST. */
 }
 
 /* Displays a table giving information on fixed-format variable
    parsing on DATA LIST. */
-/* FIXME: The `Columns' column should be divided into three columns,
-   one for the starting column, one for the dash, one for the ending
-   column; then right-justify the starting column and left-justify the
-   ending column. */
 static void
 static void
-dump_fixed_table (const struct dls_var_spec *specs,
-                  const struct file_handle *fh, int rec_cnt)
+dump_fixed_table (const struct ll_list *specs,
+                  const struct file_handle *fh, int record_cnt)
 {
 {
-  const struct dls_var_spec *spec;
+  size_t spec_cnt;
   struct tab_table *t;
   struct tab_table *t;
-  int i;
+  struct dls_var_spec *spec;
+  int row;
 
 
-  for (i = 0, spec = specs; spec; spec = spec->next)
-    i++;
-  t = tab_create (4, i + 1, 0);
+  spec_cnt = ll_count (specs);
+  t = tab_create (4, spec_cnt + 1, 0);
   tab_columns (t, TAB_COL_DOWN, 1);
   tab_headers (t, 0, 0, 1, 0);
   tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Variable"));
   tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Record"));
   tab_text (t, 2, 0, TAB_CENTER | TAT_TITLE, _("Columns"));
   tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Format"));
   tab_columns (t, TAB_COL_DOWN, 1);
   tab_headers (t, 0, 0, 1, 0);
   tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Variable"));
   tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Record"));
   tab_text (t, 2, 0, TAB_CENTER | TAT_TITLE, _("Columns"));
   tab_text (t, 3, 0, TAB_CENTER | TAT_TITLE, _("Format"));
-  tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 3, i);
+  tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 3, spec_cnt);
   tab_hline (t, TAL_2, 0, 3, 1);
   tab_dim (t, tab_natural_dimensions);
 
   tab_hline (t, TAL_2, 0, 3, 1);
   tab_dim (t, tab_natural_dimensions);
 
-  for (i = 1, spec = specs; spec; spec = spec->next, i++)
+  row = 1;
+  ll_for_each (spec, struct dls_var_spec, ll, specs)
     {
     {
-      tab_text (t, 0, i, TAB_LEFT, spec->v->name);
-      tab_text (t, 1, i, TAT_PRINTF, "%d", spec->rec);
-      tab_text (t, 2, i, TAT_PRINTF, "%3d-%3d",
-                   spec->fc, spec->lc);
-      tab_text (t, 3, i, TAB_LEFT | TAB_FIX,
-                   fmt_to_string (&spec->input));
+      char fmt_string[FMT_STRING_LEN_MAX + 1];
+      tab_text (t, 0, row, TAB_LEFT, spec->name);
+      tab_text (t, 1, row, TAT_PRINTF, "%d", spec->record);
+      tab_text (t, 2, row, TAT_PRINTF, "%3d-%3d",
+                spec->first_column, spec->first_column + spec->input.w - 1);
+      tab_text (t, 3, row, TAB_LEFT | TAB_FIX,
+                fmt_to_string (&spec->input, fmt_string));
+      row++;
     }
 
   tab_title (t, ngettext ("Reading %d record from %s.",
     }
 
   tab_title (t, ngettext ("Reading %d record from %s.",
-                          "Reading %d records from %s.", rec_cnt),
-             rec_cnt, fh_get_name (fh));
+                          "Reading %d records from %s.", record_cnt),
+             record_cnt, fh_get_name (fh));
   tab_submit (t);
 }
 \f
 /* Free-format parsing. */
 
 /* Parses variable specifications for DATA LIST FREE and adds
   tab_submit (t);
 }
 \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 (struct dls_var_spec **first, struct dls_var_spec **last)
+   them to DLS.  Uses TMP_POOL for data that is not needed once
+   parsing is complete.  Returns true only if successful. */
+static bool
+parse_free (struct lexer *lexer, struct dictionary *dict, struct pool *tmp_pool,
+               struct data_list_pgm *dls)
 {
 {
-  lex_get ();
-  while (token != '.')
+  lex_get (lexer);
+  while (lex_token (lexer) != '.')
     {
       struct fmt_spec input, output;
       char **name;
       size_t name_cnt;
     {
       struct fmt_spec input, output;
       char **name;
       size_t name_cnt;
-      int width;
       size_t i;
 
       size_t i;
 
-      if (!parse_DATA_LIST_vars (&name, &name_cnt, PV_NONE))
+      if (!parse_DATA_LIST_vars_pool (lexer, tmp_pool,
+                                     &name, &name_cnt, PV_NONE))
        return 0;
 
        return 0;
 
-      if (lex_match ('('))
+      if (lex_match (lexer, '('))
        {
        {
-         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);
+         if (!parse_format_specifier (lexer, &input)
+              || !fmt_check_input (&input)
+              || !lex_force_match (lexer, ')'))
+            return NULL;
+
+          /* As a special case, N format is treated as F format
+             for free-field input. */
+          if (input.type == FMT_N)
+            input.type = FMT_F;
+
+         output = fmt_for_output_from_input (&input);
        }
       else
        {
        }
       else
        {
-         lex_match ('*');
-          input = make_input_format (FMT_F, 8, 0);
+         lex_match (lexer, '*');
+          input = fmt_for_input (FMT_F, 8, 0);
          output = *get_format ();
        }
 
          output = *get_format ();
        }
 
-      if (input.type == FMT_A || input.type == FMT_AHEX)
-       width = input.w;
-      else
-       width = 0;
       for (i = 0; i < name_cnt; i++)
        {
           struct dls_var_spec *spec;
          struct variable *v;
 
       for (i = 0; i < name_cnt; i++)
        {
           struct dls_var_spec *spec;
          struct variable *v;
 
-         v = dict_create_var (default_dict, name[i], width);
-         
-         if (!v)
+         v = dict_create_var (dict, name[i], fmt_var_width (&input));
+         if (v == NULL)
            {
              msg (SE, _("%s is a duplicate variable name."), name[i]);
              return 0;
            }
            {
              msg (SE, _("%s is a duplicate variable name."), name[i]);
              return 0;
            }
-         v->print = v->write = output;
-
-          if (!case_source_is_complex (vfm_source))
-            v->init = 0;
+          var_set_both_formats (v, &output);
 
 
-          spec = xmalloc (sizeof *spec);
+          spec = pool_alloc (dls->pool, sizeof *spec);
           spec->input = input;
           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);
+         spec->fv = var_get_case_index (v);
+         strcpy (spec->name, var_get_name (v));
+          ll_push_tail (&dls->specs, &spec->ll);
        }
        }
-      for (i = 0; i < name_cnt; i++)
-       free (name[i]);
-      free (name);
     }
 
     }
 
-  return lex_end_of_command () == CMD_SUCCESS;
+  return true;
 }
 
 /* Displays a table giving information on free-format variable parsing
 }
 
 /* Displays a table giving information on free-format variable parsing
@@ -885,145 +520,106 @@ dump_free_table (const struct data_list_pgm *dls,
                  const struct file_handle *fh)
 {
   struct tab_table *t;
                  const struct file_handle *fh)
 {
   struct tab_table *t;
-  int i;
-  
-  {
-    struct dls_var_spec *spec;
-    for (i = 0, spec = dls->first; spec; spec = spec->next)
-      i++;
-  }
-  
-  t = tab_create (2, i + 1, 0);
+  struct dls_var_spec *spec;
+  size_t spec_cnt;
+  int row;
+
+  spec_cnt = ll_count (&dls->specs);
+
+  t = tab_create (2, spec_cnt + 1, 0);
   tab_columns (t, TAB_COL_DOWN, 1);
   tab_headers (t, 0, 0, 1, 0);
   tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Variable"));
   tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Format"));
   tab_columns (t, TAB_COL_DOWN, 1);
   tab_headers (t, 0, 0, 1, 0);
   tab_text (t, 0, 0, TAB_CENTER | TAT_TITLE, _("Variable"));
   tab_text (t, 1, 0, TAB_CENTER | TAT_TITLE, _("Format"));
-  tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 1, i);
+  tab_box (t, TAL_1, TAL_1, TAL_0, TAL_1, 0, 0, 1, spec_cnt);
   tab_hline (t, TAL_2, 0, 1, 1);
   tab_dim (t, tab_natural_dimensions);
   tab_hline (t, TAL_2, 0, 1, 1);
   tab_dim (t, tab_natural_dimensions);
-  
-  {
-    struct dls_var_spec *spec;
-    
-    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 | TAB_FIX, fmt_to_string (&spec->input));
-      }
-  }
+  row = 1;
+  ll_for_each (spec, struct dls_var_spec, ll, &dls->specs)
+    {
+      char str[FMT_STRING_LEN_MAX + 1];
+      tab_text (t, 0, row, TAB_LEFT, spec->name);
+      tab_text (t, 1, row, TAB_LEFT | TAB_FIX,
+                fmt_to_string (&spec->input, str));
+      row++;
+    }
 
   tab_title (t, _("Reading free-form data from %s."), fh_get_name (fh));
 
   tab_title (t, _("Reading free-form data from %s."), fh_get_name (fh));
-  
+
   tab_submit (t);
 }
 \f
   tab_submit (t);
 }
 \f
-/* Input procedure. */ 
+/* Input procedure. */
 
 /* Extracts a field from the current position in the current
    record.  Fields can be unquoted or quoted with single- or
 
 /* 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.
+   double-quote characters.
+
+   *FIELD is set to the field content.  The caller must not
+   or destroy this constant string.
+
    After parsing the field, sets the current position in the
    record to just past the field and any trailing delimiter.
    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 (const struct data_list_pgm *dls, struct fixed_string *field,
-           int *end_blank)
+   Returns 0 on failure or a 1-based column number indicating the
+   beginning of the field on success. */
+static bool
+cut_field (const struct data_list_pgm *dls, struct substring *field)
 {
 {
-  struct fixed_string line;
-  char *cp;
-  size_t column_start;
+  struct substring line, p;
 
   if (dfm_eof (dls->reader))
 
   if (dfm_eof (dls->reader))
-    return 0;
-  if (dls->delim_cnt == 0)
+    return false;
+  if (ds_is_empty (&dls->delims))
     dfm_expand_tabs (dls->reader);
     dfm_expand_tabs (dls->reader);
-  dfm_get_record (dls->reader, &line);
+  line = p = dfm_get_record (dls->reader);
 
 
-  cp = ls_c_str (&line);
-  if (dls->delim_cnt == 0) 
+  if (ds_is_empty (&dls->delims))
     {
     {
+      bool missing_quote = false;
+
       /* Skip leading whitespace. */
       /* Skip leading whitespace. */
-      while (cp < ls_end (&line) && isspace ((unsigned char) *cp))
-        cp++;
-      if (cp >= ls_end (&line))
-        return 0;
-      
+      ss_ltrim (&p, ss_cstr (CC_SPACES));
+      if (ss_is_empty (p))
+        return false;
+
       /* Handle actual data, whether quoted or unquoted. */
       /* 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);
-        }
+      if (ss_match_char (&p, '\''))
+        missing_quote = !ss_get_until (&p, '\'', field);
+      else if (ss_match_char (&p, '"'))
+        missing_quote = !ss_get_until (&p, '"', field);
       else
       else
-        {
-          field->string = cp;
-          while (cp < ls_end (&line)
-                 && !isspace ((unsigned char) *cp) && *cp != ',')
-            cp++;
-          field->length = cp - field->string;
-        }
+        ss_get_chars (&p, ss_cspan (p, ss_cstr ("," CC_SPACES)), field);
+      if (missing_quote)
+        msg (SW, _("Quoted string extends beyond end of line."));
 
       /* Skip trailing whitespace and a single comma if present. */
 
       /* 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++;
+      ss_ltrim (&p, ss_cstr (CC_SPACES));
+      ss_match_char (&p, ',');
+
+      dfm_forward_columns (dls->reader, ss_length (line) - ss_length (p));
     }
     }
-  else 
+  else
     {
     {
-      if (cp >= ls_end (&line)) 
+      if (!ss_is_empty (p))
+        ss_get_chars (&p, ss_cspan (p, ds_ss (&dls->delims)), field);
+      else if (dfm_columns_past_end (dls->reader) == 0)
         {
         {
-          int column = dfm_column_start (dls->reader);
-               /* A blank line or a line that ends in \t has a
+          /* A blank line or a line that ends in a delimiter has a
              trailing blank field. */
              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++;
+          *field = p;
         }
         }
+      else
+        return false;
+
+      /* Advance past the field.
+
+         Also advance past a trailing delimiter, regardless of
+         whether one actually existed.  If we "skip" a delimiter
+         that was not actually there, then we will return
+         end-of-line on our next call, which is what we want. */
+      dfm_forward_columns (dls->reader, ss_length (line) - ss_length (p) + 1);
     }
     }
-  
-  dfm_forward_columns (dls->reader, field->string - line.string);
-  column_start = dfm_column_start (dls->reader);
-    
-  dfm_forward_columns (dls->reader, cp - field->string);
-    
-  return column_start;
+  return true;
 }
 
 static bool read_from_data_list_fixed (const struct data_list_pgm *,
 }
 
 static bool read_from_data_list_fixed (const struct data_list_pgm *,
@@ -1036,7 +632,7 @@ static bool read_from_data_list_list (const struct data_list_pgm *,
 /* Reads a case from DLS into C.
    Returns true if successful, false at end of file or on I/O error. */
 static bool
 /* Reads a case from DLS into C.
    Returns true if successful, false at end of file or on I/O error. */
 static bool
-read_from_data_list (const struct data_list_pgm *dls, struct ccase *c) 
+read_from_data_list (const struct data_list_pgm *dls, struct ccase *c)
 {
   bool retval;
 
 {
   bool retval;
 
@@ -1053,7 +649,7 @@ read_from_data_list (const struct data_list_pgm *dls, struct ccase *c)
       retval = read_from_data_list_list (dls, c);
       break;
     default:
       retval = read_from_data_list_list (dls, c);
       break;
     default:
-      abort ();
+      NOT_REACHED ();
     }
   dfm_pop (dls->reader);
 
     }
   dfm_pop (dls->reader);
 
@@ -1061,43 +657,41 @@ read_from_data_list (const struct data_list_pgm *dls, struct ccase *c)
 }
 
 /* Reads a case from the data file into C, parsing it according
 }
 
 /* Reads a case from the data file into C, parsing it according
-   to fixed-format syntax rules in DLS.  
+   to fixed-format syntax rules in DLS.
    Returns true if successful, false at end of file or on I/O error. */
 static bool
 read_from_data_list_fixed (const struct data_list_pgm *dls, struct ccase *c)
 {
    Returns true if successful, false at end of file or on I/O error. */
 static bool
 read_from_data_list_fixed (const struct data_list_pgm *dls, struct ccase *c)
 {
-  struct dls_var_spec *var_spec = dls->first;
-  int i;
+  struct dls_var_spec *spec;
+  int row;
 
   if (dfm_eof (dls->reader))
     return false;
 
   if (dfm_eof (dls->reader))
     return false;
-  for (i = 1; i <= dls->rec_cnt; i++)
+
+  spec = ll_to_dls_var_spec (ll_head (&dls->specs));
+  for (row = 1; row <= dls->record_cnt; row++)
     {
     {
-      struct fixed_string line;
-      
+      struct substring line;
+
       if (dfm_eof (dls->reader))
       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, dls->rec_cnt);
-         return false;
-       }
+        {
+          msg (SW, _("Partial case of %d of %d records discarded."),
+               row - 1, dls->record_cnt);
+          return false;
+        }
       dfm_expand_tabs (dls->reader);
       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;
+      line = dfm_get_record (dls->reader);
 
 
-         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;
+      ll_for_each_continue (spec, struct dls_var_spec, ll, &dls->specs)
+        {
+          if (row < spec->record)
+            break;
 
 
-         data_in (&di);
-       }
+          data_in (ss_substr (line, spec->first_column - 1, spec->input.w),
+                   spec->input.type, spec->input.d, spec->first_column,
+                   case_data_rw_idx (c, spec->fv),
+                   fmt_var_width (&spec->input));
+        }
 
       dfm_forward_record (dls->reader);
     }
 
       dfm_forward_record (dls->reader);
     }
@@ -1106,156 +700,114 @@ read_from_data_list_fixed (const struct data_list_pgm *dls, struct ccase *c)
 }
 
 /* Reads a case from the data file into C, parsing it according
 }
 
 /* Reads a case from the data file into C, parsing it according
-   to free-format syntax rules in DLS.  
+   to free-format syntax rules in DLS.
    Returns true if successful, false at end of file or on I/O error. */
 static bool
 read_from_data_list_free (const struct data_list_pgm *dls, struct ccase *c)
 {
    Returns true if successful, false at end of file or on I/O error. */
 static bool
 read_from_data_list_free (const struct data_list_pgm *dls, struct ccase *c)
 {
-  struct dls_var_spec *var_spec;
-  int end_blank = 0;
+  struct dls_var_spec *spec;
 
 
-  for (var_spec = dls->first; var_spec; var_spec = var_spec->next)
+  ll_for_each (spec, struct dls_var_spec, ll, &dls->specs)
     {
     {
-      struct fixed_string field;
-      int column;
-      
+      struct substring field;
+
       /* Cut out a field and read in a new record if necessary. */
       /* Cut out a field and read in a new record if necessary. */
-      for (;;)
+      while (!cut_field (dls, &field))
        {
        {
-         column = cut_field (dls, &field, &end_blank);
-         if (column != 0)
-           break;
-
-         if (!dfm_eof (dls->reader)) 
+         if (!dfm_eof (dls->reader))
             dfm_forward_record (dls->reader);
          if (dfm_eof (dls->reader))
            {
             dfm_forward_record (dls->reader);
          if (dfm_eof (dls->reader))
            {
-             if (var_spec != dls->first)
+             if (&spec->ll != ll_head (&dls->specs))
                msg (SW, _("Partial case discarded.  The first variable "
                msg (SW, _("Partial case discarded.  The first variable "
-                           "missing was %s."), var_spec->name);
+                           "missing was %s."), spec->name);
              return false;
            }
        }
              return false;
            }
        }
-      
-      {
-       struct data_in di;
-
-       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;
-       data_in (&di);
-      }
+
+      data_in (field, spec->input.type, 0,
+               dfm_get_column (dls->reader, ss_data (field)),
+               case_data_rw_idx (c, spec->fv), fmt_var_width (&spec->input));
     }
   return true;
 }
 
 /* Reads a case from the data file and parses it according to
     }
   return true;
 }
 
 /* Reads a case from the data file and parses it according to
-   list-format syntax rules.  
+   list-format syntax rules.
    Returns true if successful, false at end of file or on I/O error. */
 static bool
 read_from_data_list_list (const struct data_list_pgm *dls, struct ccase *c)
 {
    Returns true if successful, false at end of file or on I/O error. */
 static bool
 read_from_data_list_list (const struct data_list_pgm *dls, struct ccase *c)
 {
-  struct dls_var_spec *var_spec;
-  int end_blank = 0;
+  struct dls_var_spec *spec;
 
   if (dfm_eof (dls->reader))
     return false;
 
 
   if (dfm_eof (dls->reader))
     return false;
 
-  for (var_spec = dls->first; var_spec; var_spec = var_spec->next)
+  ll_for_each (spec, struct dls_var_spec, ll, &dls->specs)
     {
     {
-      struct fixed_string field;
-      int column;
+      struct substring field;
 
 
-      /* Cut out a field and check for end-of-line. */
-      column = cut_field (dls, &field, &end_blank);
-      if (column == 0)
+      if (!cut_field (dls, &field))
        {
          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."),
        {
          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."),
-                var_spec->name);
-         for (; var_spec; var_spec = var_spec->next)
+                spec->name);
+          ll_for_each_continue (spec, struct dls_var_spec, ll, &dls->specs)
             {
             {
-              int width = get_format_var_width (&var_spec->input);
+              int width = fmt_var_width (&spec->input);
               if (width == 0)
               if (width == 0)
-                case_data_rw (c, var_spec->fv)->f = SYSMIS;
+                case_data_rw_idx (c, spec->fv)->f = SYSMIS;
               else
               else
-                memset (case_data_rw (c, var_spec->fv)->s, ' ', width); 
+                memset (case_data_rw_idx (c, spec->fv)->s, ' ', width);
             }
          break;
        }
             }
          break;
        }
-      
-      {
-       struct data_in di;
-
-       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;
-       data_in (&di);
-      }
+
+      data_in (field, spec->input.type, 0,
+               dfm_get_column (dls->reader, ss_data (field)),
+               case_data_rw_idx (c, spec->fv), fmt_var_width (&spec->input));
     }
 
   dfm_forward_record (dls->reader);
   return true;
 }
 
     }
 
   dfm_forward_record (dls->reader);
   return true;
 }
 
-/* Destroys SPEC. */
-static void
-destroy_dls_var_spec (struct dls_var_spec *spec) 
-{
-  struct dls_var_spec *next;
-
-  while (spec != NULL)
-    {
-      next = spec->next;
-      free (spec);
-      spec = next;
-    }
-}
-
 /* Destroys DATA LIST transformation DLS.
    Returns true if successful, false if an I/O error occurred. */
 static bool
 data_list_trns_free (void *dls_)
 {
   struct data_list_pgm *dls = dls_;
 /* Destroys DATA LIST transformation DLS.
    Returns true if successful, false if an I/O error occurred. */
 static bool
 data_list_trns_free (void *dls_)
 {
   struct data_list_pgm *dls = dls_;
-  free (dls->delims);
-  destroy_dls_var_spec (dls->first);
   dfm_close_reader (dls->reader);
   dfm_close_reader (dls->reader);
-  free (dls);
+  pool_destroy (dls->pool);
   return true;
 }
 
 /* Handle DATA LIST transformation DLS, parsing data into C. */
 static int
   return true;
 }
 
 /* Handle DATA LIST transformation DLS, parsing data into C. */
 static int
-data_list_trns_proc (void *dls_, struct ccase *c, int case_num UNUSED)
+data_list_trns_proc (void *dls_, struct ccase *c, casenumber case_num UNUSED)
 {
   struct data_list_pgm *dls = dls_;
   int retval;
 
   if (read_from_data_list (dls, c))
     retval = TRNS_CONTINUE;
 {
   struct data_list_pgm *dls = dls_;
   int retval;
 
   if (read_from_data_list (dls, c))
     retval = TRNS_CONTINUE;
-  else if (dfm_reader_error (dls->reader) || dfm_eof (dls->reader) > 1) 
+  else if (dfm_reader_error (dls->reader) || dfm_eof (dls->reader) > 1)
     {
       /* An I/O error, or encountering end of file for a second
          time, should be escalated into a more serious error. */
       retval = TRNS_ERROR;
     }
   else
     {
       /* An I/O error, or encountering end of file for a second
          time, should be escalated into a more serious error. */
       retval = TRNS_ERROR;
     }
   else
-    retval = TRNS_DROP_CASE;
-  
+    retval = TRNS_END_FILE;
+
   /* If there was an END subcommand handle it. */
   /* If there was an END subcommand handle it. */
-  if (dls->end != NULL) 
+  if (dls->end != NULL)
     {
     {
-      double *end = &case_data_rw (c, dls->end->fv)->f;
-      if (retval == TRNS_DROP_CASE)
+      double *end = &case_data_rw (c, dls->end)->f;
+      if (retval == TRNS_END_FILE)
         {
           *end = 1.0;
           retval = TRNS_CONTINUE;
         {
           *end = 1.0;
           retval = TRNS_CONTINUE;
@@ -1267,793 +819,47 @@ data_list_trns_proc (void *dls_, struct ccase *c, int case_num UNUSED)
   return retval;
 }
 \f
   return retval;
 }
 \f
-/* Reads all the records from the data file and passes them to
-   write_case().
-   Returns true if successful, false if an I/O error occurred. */
+/* Reads one case into OUTPUT_CASE.
+   Returns true if successful, false at end of file or if an
+   I/O error occurred. */
 static bool
 static bool
-data_list_source_read (struct case_source *source,
-                       struct ccase *c,
-                       write_case_func *write_case, write_case_data wc_data)
+data_list_casereader_read (struct casereader *reader UNUSED, void *dls_,
+                           struct ccase *c)
 {
 {
-  struct data_list_pgm *dls = source->aux;
+  struct data_list_pgm *dls = dls_;
+  bool ok;
 
 
-  for (;;) 
+  /* Skip the requested number of records before reading the
+     first case. */
+  while (dls->skip_records > 0)
     {
     {
-      bool ok;
-
-      if (!read_from_data_list (dls, c)) 
-        return !dfm_reader_error (dls->reader);
-
-      dfm_push (dls->reader);
-      ok = write_case (wc_data);
-      dfm_pop (dls->reader);
-      if (!ok)
+      if (dfm_eof (dls->reader))
         return false;
         return false;
+      dfm_forward_record (dls->reader);
+      dls->skip_records--;
     }
     }
+
+  case_create (c, dls->value_cnt);
+  ok = read_from_data_list (dls, c);
+  if (!ok)
+    case_destroy (c);
+  return ok;
 }
 
 }
 
-/* Destroys the source's internal data. */
+/* Destroys the casereader. */
 static void
 static void
-data_list_source_destroy (struct case_source *source)
+data_list_casereader_destroy (struct casereader *reader UNUSED, void *dls_)
 {
 {
-  data_list_trns_free (source->aux);
+  struct data_list_pgm *dls = dls_;
+  if (dfm_reader_error (dls->reader))
+    casereader_force_error (reader);
+  data_list_trns_free (dls);
 }
 
 }
 
-static const struct case_source_class data_list_source_class = 
+static const struct casereader_class data_list_casereader_class =
   {
   {
-    "DATA LIST",
+    data_list_casereader_read,
+    data_list_casereader_destroy,
+    NULL,
     NULL,
     NULL,
-    data_list_source_read,
-    data_list_source_destroy,
-  };
-\f
-/* REPEATING DATA. */
-
-/* Represents a number or a variable. */
-struct rpd_num_or_var
-  {
-    int num;                   /* Value, or 0. */
-    struct variable *var;      /* Variable, if number==0. */
-  };
-    
-/* REPEATING DATA private data structure. */
-struct repeating_data_trns
-  {
-    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. */
-    struct rpd_num_or_var occurs;      /* OCCURS= subcommand. */
-    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. */
-
-    /* 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. */
-
-    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 (struct dls_var_spec **,
-                                 struct dls_var_spec **);
-static void find_variable_input_spec (struct variable *v,
-                                     struct fmt_spec *spec);
-
-/* Parses the REPEATING DATA command. */
-int
-cmd_repeating_data (void)
-{
-  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 = fh_get_default_handle ();
-  
-  assert (case_source_is_complex (vfm_source));
-
-  rpd = xmalloc (sizeof *rpd);
-  rpd->reader = dfm_open_reader (fh);
-  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 ('/');
-  
-  for (;;)
-    {
-      if (lex_match_id ("FILE"))
-       {
-          struct file_handle *file;
-         lex_match ('=');
-         file = fh_parse (FH_REF_FILE | FH_REF_INLINE);
-         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."));
-              goto error;
-           }
-       }
-      else if (lex_match_id ("STARTS"))
-       {
-         lex_match ('=');
-         if (saw_starts)
-           {
-             msg (SE, _("%s subcommand given multiple times."),"STARTS");
-             goto error;
-           }
-          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"))
-               goto error;
-           } else {
-             /* 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)
-           {
-             msg (SE, _("STARTS beginning column (%d) exceeds "
-                        "STARTS ending column (%d)."),
-                  rpd->starts_beg.num, rpd->starts_end.num);
-             goto error;
-           }
-       }
-      else if (lex_match_id ("OCCURS"))
-       {
-         lex_match ('=');
-         if (saw_occurs)
-           {
-             msg (SE, _("%s subcommand given multiple times."),"OCCURS");
-             goto error;
-           }
-         saw_occurs = true;
-
-         if (!parse_num_or_var (&rpd->occurs, "OCCURS"))
-           goto error;
-       }
-      else if (lex_match_id ("LENGTH"))
-       {
-         lex_match ('=');
-         if (saw_length)
-           {
-             msg (SE, _("%s subcommand given multiple times."),"LENGTH");
-             goto error;
-           }
-         saw_length = true;
-
-         if (!parse_num_or_var (&rpd->length, "LENGTH"))
-           goto error;
-       }
-      else if (lex_match_id ("CONTINUED"))
-       {
-         lex_match ('=');
-         if (saw_continued)
-           {
-             msg (SE, _("%s subcommand given multiple times."),"CONTINUED");
-             goto error;
-           }
-         saw_continued = true;
-
-         if (!lex_match ('/'))
-           {
-             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,
-                                       "CONTINUED ending column"))
-               goto error;
-         
-             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);
-                 goto error;
-               }
-           }
-         else
-           rpd->cont_beg.num = 1;
-       }
-      else if (lex_match_id ("ID"))
-       {
-         lex_match ('=');
-         if (saw_id)
-           {
-             msg (SE, _("%s subcommand given multiple times."),"ID");
-             goto error;
-           }
-         saw_id = true;
-         
-         if (!lex_force_int ())
-           goto error;
-         if (lex_integer () < 1)
-           {
-             msg (SE, _("ID beginning column (%ld) must be positive."),
-                  lex_integer ());
-             goto error;
-           }
-         rpd->id_beg = lex_integer ();
-         
-         lex_get ();
-         lex_negative_to_dash ();
-         
-         if (lex_match ('-'))
-           {
-             if (!lex_force_int ())
-               goto error;
-             if (lex_integer () < 1)
-               {
-                 msg (SE, _("ID ending column (%ld) must be positive."),
-                      lex_integer ());
-                 goto error;
-               }
-             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);
-                 goto error;
-               }
-             
-             rpd->id_end = lex_integer ();
-             lex_get ();
-           }
-         else rpd->id_end = rpd->id_beg;
-
-         if (!lex_force_match ('='))
-           goto error;
-         rpd->id_var = parse_variable ();
-         if (rpd->id_var == NULL)
-           goto error;
-
-         find_variable_input_spec (rpd->id_var, &rpd->id_spec);
-          rpd->id_value = xnmalloc (rpd->id_var->nv, sizeof *rpd->id_value);
-       }
-      else if (lex_match_id ("TABLE"))
-       table = 1;
-      else if (lex_match_id ("NOTABLE"))
-       table = 0;
-      else if (lex_match_id ("DATA"))
-       break;
-      else
-       {
-         lex_error (NULL);
-         goto error;
-       }
-
-      if (!lex_force_match ('/'))
-       goto error;
-    }
-
-  /* Comes here when DATA specification encountered. */
-  if (!saw_starts || !saw_occurs)
-    {
-      if (!saw_starts)
-       msg (SE, _("Missing required specification STARTS."));
-      if (!saw_occurs)
-       msg (SE, _("Missing required specification OCCURS."));
-      goto error;
-    }
-
-  /* Enforce ID restriction. */
-  if (saw_id && !saw_continued)
-    {
-      msg (SE, _("ID specified without CONTINUED."));
-      goto error;
-    }
-
-  /* Calculate and check starts_end, cont_end if necessary. */
-  if (rpd->starts_end.num == 0 && rpd->starts_end.var == NULL) 
-    {
-      rpd->starts_end.num = fh_get_record_width (fh);
-      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_get_record_width (fh);
-      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 (&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 (rpd->first, fh, rpd->last->rec);
-
-  add_transformation (repeating_data_trns_proc, repeating_data_trns_free, rpd);
-
-  return lex_end_of_command ();
-
- error:
-  repeating_data_trns_free (rpd);
-  return CMD_CASCADING_FAILURE;
-}
-
-/* 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)
-{
-  size_t i;
-  
-  for (i = 0; i < n_trns; i++)
-    {
-      struct transformation *trns = &t_trns[i];
-      
-      if (trns->proc == data_list_trns_proc)
-       {
-          struct data_list_pgm *pgm = trns->private;
-         struct dls_var_spec *iter;
-
-         for (iter = pgm->first; iter; iter = iter->next)
-           if (iter->v == v)
-             {
-               *spec = iter->input;
-               return;
-             }
-       }
-    }
-  
-  assert (0);
-}
-
-/* Parses a number or a variable name from the syntax file and puts
-   the results in VALUE.  Ensures that the number is at least 1; else
-   emits an error based on MESSAGE.  Returns nonzero only if
-   successful. */
-static int
-parse_num_or_var (struct rpd_num_or_var *value, const char *message)
-{
-  if (token == T_ID)
-    {
-      value->num = 0;
-      value->var = parse_variable ();
-      if (value->var == NULL)
-       return 0;
-      if (value->var->type == ALPHA)
-       {
-         msg (SE, _("String variable not allowed here."));
-         return 0;
-       }
-    }
-  else if (lex_is_integer ())
-    {
-      value->num = lex_integer ();
-      
-      if (value->num < 1)
-       {
-         msg (SE, _("%s (%d) must be at least 1."), message, value->num);
-         return 0;
-       }
-      
-      lex_get ();
-    } else {
-      msg (SE, _("Variable or integer expected for %s."), message);
-      return 0;
-    }
-  return 1;
-}
-
-/* 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 (struct dls_var_spec **first, struct dls_var_spec **last)
-{
-  struct fixed_parsing_state fx;
-  size_t i;
-
-  fx.recno = 0;
-  fx.sc = 1;
-
-  while (token != '.')
-    {
-      if (!parse_DATA_LIST_vars (&fx.name, &fx.name_cnt, PV_NONE))
-       return 0;
-
-      if (lex_is_number ())
-       {
-         if (!fixed_parse_compatible (&fx, first, last))
-           goto fail;
-       }
-      else if (token == '(')
-       {
-         if (!fixed_parse_fortran (&fx, first, last))
-           goto fail;
-       }
-      else
-       {
-         msg (SE, _("SPSS-like or FORTRAN-like format "
-                     "specification expected after variable names."));
-         goto fail;
-       }
-
-      for (i = 0; i < fx.name_cnt; i++)
-       free (fx.name[i]);
-      free (fx.name);
-    }
-  
-  return 1;
-
- fail:
-  for (i = 0; i < fx.name_cnt; i++)
-    free (fx.name[i]);
-  free (fx.name);
-  return 0;
-}
-
-/* Obtains the real value for rpd_num_or_var N in case C and returns
-   it.  The valid range is nonnegative numbers, but numbers outside
-   this range can be returned and should be handled by the caller as
-   invalid. */
-static int
-realize_value (struct rpd_num_or_var *n, struct ccase *c)
-{
-  if (n->var != NULL)
-    {
-      double v = case_num (c, n->var->fv);
-      return v != SYSMIS && v >= INT_MIN && v <= INT_MAX ? v : -1;
-    }
-  else
-    return n->num;
-}
-
-/* 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 (const struct rpd_parse_info *info)
-{
-  struct repeating_data_trns *t = info->trns;
-  int cur = info->beg;
-  int occurrences;
-
-  /* Handle record ID values. */
-  if (t->id_beg != 0)
-    {
-      union value id_temp[MAX_ELEMS_PER_VALUE];
-      
-      /* Parse record ID into V. */
-      {
-       struct data_in di;
-
-       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;
-
-       if (!data_in (&di))
-         return 0;
-      }
-
-      if (info->verify_id
-          && compare_values (id_temp, t->id_value, t->id_var->width) != 0)
-       {
-         char expected_str [MAX_FORMATTED_LEN + 1];
-         char actual_str [MAX_FORMATTED_LEN + 1];
-
-         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, 
-               _("Encountered mismatched record ID \"%s\" expecting \"%s\"."),
-               actual_str, expected_str);
-
-         return 0;
-       }
-    }
-
-  /* Iterate over the set of expected occurrences and record each of
-     them as a separate case.  FIXME: We need to execute any
-     transformations that follow the current one. */
-  {
-    int warned = 0;
-
-    for (occurrences = 0; occurrences < info->max_occurs; )
-      {
-       if (cur + info->ofs > info->end + 1)
-         break;
-       occurrences++;
-
-       {
-         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 > 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, info->len);
-               }
-             
-             {
-               struct data_in di;
-
-               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;
-
-               if (!data_in (&di))
-                 return 0;
-             }
-           }
-       }
-
-       cur += info->ofs;
-
-       if (!t->write_case (t->wc_data))
-         return 0;
-      }
-  }
-
-  return occurrences;
-}
-
-/* Reads one set of repetitions of the elements in the REPEATING
-   DATA structure.  Returns TRNS_CONTINUE on success,
-   TRNS_DROP_CASE on end of file or on failure, or TRNS_NEXT_CASE. */
-int
-repeating_data_trns_proc (void *trns_, struct ccase *c, int case_num UNUSED)
-{
-  struct repeating_data_trns *t = trns_;
-    
-  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 occurs_left;     /* Number of occurrences remaining. */
-
-  int code;            /* Return value from rpd_parse_record(). */
-    
-  int skip_first_record = 0;
-    
-  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 TRNS_DROP_CASE;
-  dfm_get_record (t->reader, &line);
-  dfm_forward_record (t->reader);
-
-  /* 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 TRNS_NEXT_CASE;
-    }
-  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 TRNS_NEXT_CASE;
-    }
-  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 TRNS_DROP_CASE;
-    }
-  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 TRNS_DROP_CASE;
-    }
-
-  /* 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 TRNS_DROP_CASE;
-      occurs_left -= code;
-    }
-  else if (cont_beg == 0)
-    return TRNS_NEXT_CASE;
-
-  /* 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 TRNS_DROP_CASE;
-    }
-
-  /* Go on to additional records. */
-  while (occurs_left != 0)
-    {
-      struct rpd_parse_info info;
-
-      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 TRNS_DROP_CASE;
-        }
-      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 TRNS_DROP_CASE;
-      occurs_left -= code;
-    }
-    
-  dfm_pop (t->reader);
-
-  /* FIXME: This is a kluge until we've implemented multiplexing of
-     transformations. */
-  return TRNS_NEXT_CASE;
-}
-
-/* Frees a REPEATING DATA transformation.
-   Returns true if successful, false if an I/O error occurred. */
-bool
-repeating_data_trns_free (void *rpd_) 
-{
-  struct repeating_data_trns *rpd = rpd_;
-  
-  destroy_dls_var_spec (rpd->first);
-  dfm_close_reader (rpd->reader);
-  free (rpd->id_value);
-  free (rpd);
-  return true;
-}
-
-/* Lets repeating_data_trns_proc() know how to write the cases
-   that it composes.  Not elegant. */
-void
-repeating_data_set_write_case (struct transformation *trns_,
-                               write_case_func *write_case,
-                               write_case_data wc_data) 
-{
-  struct repeating_data_trns *t = trns_->private;
-
-  assert (trns_->proc == repeating_data_trns_proc);
-  t->write_case = write_case;
-  t->wc_data = wc_data;
-}