Changed int to bool in dict_get_weight and sort_active_file_in_place
[pspp-builds.git] / src / language / data-io / data-list.c
index 89505dbc805871b3f5cf7d02ac4c3f070ee7de62..e86c91b386e2753e3a1430bee2d04082f5ef6982 100644 (file)
 #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/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/alloc.h>
+#include <libpspp/assertion.h>
 #include <libpspp/compiler.h>
 #include <libpspp/compiler.h>
-#include <libpspp/message.h>
+#include <libpspp/ll.h>
 #include <libpspp/message.h>
 #include <libpspp/misc.h>
 #include <libpspp/message.h>
 #include <libpspp/misc.h>
+#include <libpspp/pool.h>
 #include <libpspp/str.h>
 #include <output/table.h>
 
 #include <libpspp/str.h>
 #include <output/table.h>
 
+#include "size_max.h"
+#include "xsize.h"
+
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 \f
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 \f
 /* 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,
@@ -83,26 +92,23 @@ 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. */
   };
 
 static const struct case_source_class data_list_source_class;
 
   };
 
 static const struct case_source_class data_list_source_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 pool *tmp_pool, struct data_list_pgm *);
+static bool parse_free (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;
@@ -113,18 +119,22 @@ cmd_data_list (void)
   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 (!in_input_program ())
     discard_variables ();
 
 
   if (!in_input_program ())
     discard_variables ();
 
-  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;
+  ds_init_empty (&dls->delims);
+  ds_register_pool (&dls->delims, dls->pool);
+
+  tmp_pool = pool_create_subpool (dls->pool);
 
   while (token != '/')
     {
 
   while (token != '/')
     {
@@ -141,7 +151,7 @@ cmd_data_list (void)
          lex_match ('(');
          if (!lex_force_int ())
            goto error;
          lex_match ('(');
          if (!lex_force_int ())
            goto error;
-         dls->rec_cnt = lex_integer ();
+         dls->record_cnt = lex_integer ();
          lex_get ();
          lex_match (')');
        }
          lex_get ();
          lex_match (')');
        }
@@ -199,10 +209,10 @@ cmd_data_list (void)
 
                       if (lex_match_id ("TAB"))
                         delim = '\t';
 
                       if (lex_match_id ("TAB"))
                         delim = '\t';
-                      else if (token == T_STRING && tokstr.length == 1)
+                      else if (token == T_STRING && ds_length (&tokstr) == 1)
                        {
                        {
-                         delim = tokstr.string[0];
-                         lex_get();
+                         delim = ds_first (&tokstr);
+                         lex_get ();
                        }
                       else 
                         {
                        }
                       else 
                         {
@@ -210,8 +220,7 @@ cmd_data_list (void)
                           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 (',');
                     }
@@ -225,32 +234,26 @@ cmd_data_list (void)
        }
     }
 
        }
     }
 
-  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) (tmp_pool, dls);
+  if (!ok)
+    goto error;
+
+  if (lex_end_of_command () != 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);
     }
 
@@ -263,537 +266,172 @@ cmd_data_list (void)
   else 
     proc_set_source (create_case_source (&data_list_source_class, dls));
 
   else 
     proc_set_source (create_case_source (&data_list_source_class, dls));
 
+  pool_destroy (tmp_pool);
+
   return CMD_SUCCESS;
 
  error:
   data_list_trns_free (dls);
   return CMD_CASCADING_FAILURE;
 }
   return CMD_SUCCESS;
 
  error:
   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)
+   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 pool *tmp_pool, struct data_list_pgm *dls)
 {
 {
-  struct fixed_parsing_state fx;
-  size_t i;
-
-  fx.recno = 0;
-  fx.sc = 1;
+  int last_nonempty_record;
+  int record = 0;
+  int column = 1;
 
   while (token != '.')
     {
 
   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;
-       }
+      char **names;
+      size_t name_cnt, name_idx;
+      struct fmt_spec *formats, *f;
+      size_t format_cnt;
+
+      /* Parse everything. */
+      if (!parse_record_placement (&record, &column)
+          || !parse_DATA_LIST_vars_pool (tmp_pool, &names, &name_cnt, PV_NONE)
+          || !parse_var_placements (tmp_pool, name_cnt, &formats, &format_cnt))
+        return false;
 
 
-      for (i = 0; i < fx.name_cnt; i++)
-       free (fx.name[i]);
-      free (fx.name);
+      /* 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;
+            struct variable *v;
+            struct dls_var_spec *spec;
+              
+            name = names[name_idx++];
+
+            /* Create variable. */
+            width = get_format_var_width (f);
+            v = dict_create_var (default_dict, name, width);
+            if (v != NULL)
+              {
+                /* Success. */
+                struct fmt_spec output;
+                convert_fmt_ItoO (f, &output);
+                v->print = output;
+                v->write = output;
+              }
+            else
+              {
+                /* 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 (default_dict, name);
+                if ((width != 0) != (v->width != 0))
+                  {
+                    msg (SE, _("There is already a variable %s of a "
+                               "different type."),
+                         name);
+                    return false;
+                  }
+                if (width != 0 && width != v->width)
+                  {
+                    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 = v->fv;
+            spec->record = record;
+            spec->first_column = column;
+            strcpy (spec->name, v->name);
+            ll_push_tail (&dls->specs, &spec->ll);
+
+            column += f->w;
+          }
+      assert (name_idx == name_cnt);
     }
     }
-  if (dls->first == NULL
+  if (ll_is_empty (&dls->specs)
     {
       msg (SE, _("At least one variable must be specified."));
     {
       msg (SE, _("At least one variable must be specified."));
-      return 0;
+      return false;
     }
     }
-  if (dls->rec_cnt && dls->last->rec > dls->rec_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, _("Variables are specified on records that "
                 "should not exist according to RECORDS subcommand."));
     {
       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;
+      return false;
     }
     }
-  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;
+  else if (!dls->record_cnt) 
+    dls->record_cnt = last_nonempty_record;
 
 
-  /* 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;
-       }
-      else
-       {
-         v = dict_lookup_var_assert (default_dict, fx->name[i]);
-         if (!in_input_program ())
-           {
-             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)
-{
-  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);
-    }
-}
-
-/* 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;
-            int width;
-           struct variable *v;
-
-            if (formats[f->f.type].cat & FCAT_STRING) 
-              width = f->f.w;
-            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;
-             }
-           
-            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 != ')')
-    {
-      /* 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 (',');
-    }
-  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)
-    {
-      msg (SE, _("There aren't enough format specifications "
-                 "to match the number of variable names given."));
-      return 0; 
-    }
-
-  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));
+      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));
+      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 pool *tmp_pool, struct data_list_pgm *dls)
 {
   lex_get ();
   while (token != '.')
 {
   lex_get ();
   while (token != '.')
@@ -801,23 +439,17 @@ parse_free (struct dls_var_spec **first, struct dls_var_spec **last)
       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 (tmp_pool, &name, &name_cnt, PV_NONE))
        return 0;
 
       if (lex_match ('('))
        {
        return 0;
 
       if (lex_match ('('))
        {
-         if (!parse_format_specifier (&input, 0)
+         if (!parse_format_specifier (&input)
               || !check_input_specifier (&input, 1)
               || !lex_force_match (')')) 
               || !check_input_specifier (&input, 1)
               || !lex_force_match (')')) 
-            {
-              for (i = 0; i < name_cnt; i++)
-                free (name[i]);
-              free (name);
-              return 0; 
-            }
+            return NULL;
          convert_fmt_ItoO (&input, &output);
        }
       else
          convert_fmt_ItoO (&input, &output);
        }
       else
@@ -827,37 +459,29 @@ parse_free (struct dls_var_spec **first, struct dls_var_spec **last)
          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 (default_dict, name[i],
+                               get_format_var_width (&input));
+         if (v == NULL)
            {
              msg (SE, _("%s is a duplicate variable name."), name[i]);
              return 0;
            }
          v->print = v->write = output;
 
            {
              msg (SE, _("%s is a duplicate variable name."), name[i]);
              return 0;
            }
          v->print = v->write = 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;
          spec->fv = v->fv;
-         str_copy_trunc (spec->name, sizeof spec->name, v->name);
-         append_var_spec (first, last, spec);
+         strcpy (spec->name, v->name);
+          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
@@ -867,32 +491,28 @@ 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++;
-  }
+  struct dls_var_spec *spec;
+  size_t spec_cnt;
+  int row;
+
+  spec_cnt = ll_count (&dls->specs);
   
   
-  t = tab_create (2, i + 1, 0);
+  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)
+    {
+      tab_text (t, 0, row, TAB_LEFT, spec->name);
+      tab_text (t, 1, row, TAB_LEFT | TAB_FIX, fmt_to_string (&spec->input));
+      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));
   
@@ -903,109 +523,73 @@ dump_free_table (const struct data_list_pgm *dls,
 
 /* 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;
+          *field = p;
         }
       else 
         }
       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++;
-        }
+        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 *,
@@ -1035,7 +619,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);
 
@@ -1048,38 +632,40 @@ read_from_data_list (const struct data_list_pgm *dls, struct ccase *c)
 static bool
 read_from_data_list_fixed (const struct data_list_pgm *dls, struct ccase *c)
 {
 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;
-  for (i = 1; i <= dls->rec_cnt; i++)
+  if (dfm_eof (dls->reader)) 
+    return false; 
+
+  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);
+      line = dfm_get_record (dls->reader);
 
 
-      for (; var_spec && i == var_spec->rec; var_spec = var_spec->next)
-       {
-         struct data_in di;
+      ll_for_each_continue (spec, struct dls_var_spec, ll, &dls->specs) 
+        {
+          struct data_in di;
 
 
-         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_finite_line (&di, ss_data (line), ss_length (line),
+                               spec->first_column,
+                               spec->first_column + spec->input.w - 1);
+          di.v = case_data_rw (c, spec->fv);
+          di.flags = DI_IMPLIED_DECIMALS;
+          di.f1 = spec->first_column;
+          di.format = spec->input;
 
 
-         data_in (&di);
-       }
+          data_in (&di); 
+        }
 
       dfm_forward_record (dls->reader);
     }
 
       dfm_forward_record (dls->reader);
     }
@@ -1093,43 +679,34 @@ read_from_data_list_fixed (const struct data_list_pgm *dls, struct ccase *c)
 static bool
 read_from_data_list_free (const struct data_list_pgm *dls, struct ccase *c)
 {
 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;
+      struct data_in di;
       
       /* 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)) 
             dfm_forward_record (dls->reader);
          if (dfm_eof (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);
-      }
+      di.s = ss_data (field);
+      di.e = ss_end (field);
+      di.v = case_data_rw (c, spec->fv);
+      di.flags = 0;
+      di.f1 = dfm_get_column (dls->reader, ss_data (field));
+      di.format = spec->input;
+      data_in (&di);
     }
   return true;
 }
     }
   return true;
 }
@@ -1140,78 +717,55 @@ read_from_data_list_free (const struct data_list_pgm *dls, struct ccase *c)
 static bool
 read_from_data_list_list (const struct data_list_pgm *dls, struct ccase *c)
 {
 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;
+      struct data_in di;
 
 
-      /* 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 = get_format_var_width (&spec->input);
               if (width == 0)
               if (width == 0)
-                case_data_rw (c, var_spec->fv)->f = SYSMIS;
+                case_data_rw (c, spec->fv)->f = SYSMIS;
               else
               else
-                memset (case_data_rw (c, var_spec->fv)->s, ' ', width); 
+                memset (case_data_rw (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);
-      }
+      di.s = ss_data (field);
+      di.e = ss_end (field);
+      di.v = case_data_rw (c, spec->fv);
+      di.flags = 0;
+      di.f1 = dfm_get_column (dls->reader, ss_data (field));
+      di.format = spec->input;
+      data_in (&di);
     }
 
   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;
 }
 
   return true;
 }