Fully implement arbitrary delimiters on DATA LIST, extending the half
[pspp-builds.git] / src / dfm.c
index cec6234f982b94fa33f429c5c01208d54230a520..786c33e4b1f05727357b6b80c561a1c69cbc2f48 100644 (file)
--- a/src/dfm.c
+++ b/src/dfm.c
@@ -18,7 +18,7 @@
    02111-1307, USA. */
 
 #include <config.h>
-#include <assert.h>
+#include "error.h"
 #include "dfm.h"
 #include <ctype.h>
 #include <errno.h>
 
 #include "debug-print.h"
 
+/* Flags for DFM readers. */
+enum dfm_reader_flags
+  {
+    DFM_EOF = 001,              /* At end-of-file? */
+    DFM_ADVANCE = 002,          /* Read next line on dfm_get_record() call? */
+    DFM_SAW_BEGIN_DATA = 004,   /* For inline_file only, whether we've 
+                                   already read a BEGIN DATA line. */
+    DFM_TABS_EXPANDED = 010,    /* Tabs have been expanded. */
+  };
+
 /* file_handle extension structure. */
-struct dfm_fhuser_ext
+struct dfm_reader_ext
   {
     struct file_ext file;      /* Associated file. */
 
-    char *line;                        /* Current line, not null-terminated. */
-    size_t len;                        /* Length of line. */
-
-    char *ptr;                 /* Pointer into line that is returned by
-                                  dfm_get_record(). */
-    size_t size;               /* Number of bytes allocated for line. */
-    int advance;               /* Nonzero=dfm_get_record() reads a new
-                                  record; otherwise returns current record. */
+    struct file_locator where;  /* Current location in data file. */
+    struct string line;         /* Current line. */
+    size_t pos;                 /* Offset in line of current character. */
+    struct string scratch;      /* Extra line buffer. */
+    enum dfm_reader_flags flags; /* Zero or more of DFM_*. */
   };
 
-/* These are defined at the end of this file. */
 static struct fh_ext_class dfm_r_class;
-static struct fh_ext_class dfm_w_class;
 
 static void read_record (struct file_handle *h);
-\f
-/* Internal (low level). */
 
-/* Closes the file handle H which was opened by open_file_r() or
-   open_file_w(). */
+/* Asserts that H represents a DFM reader and returns H->ext
+   converted to a struct dfm_reader_ext *. */
+static inline struct dfm_reader_ext *
+get_reader (struct file_handle *h) 
+{
+  assert (h != NULL);
+  assert (h->class == &dfm_r_class);
+  assert (h->ext != NULL);
+
+  return h->ext;
+}
+
+/* Closes file handle H opened by dfm_open_for_reading(). */
 static void
-dfm_close (struct file_handle *h)
+close_reader (struct file_handle *h)
 {
-  struct dfm_fhuser_ext *ext = h->ext;
+  struct dfm_reader_ext *ext = get_reader (h);
 
   /* Skip any remaining data on the inline file. */
   if (h == inline_file)
-    while (ext->line != NULL)
+    while ((ext->flags & DFM_EOF) == 0)
       read_record (h);
       
   msg (VM (2), _("%s: Closing data-file handle %s."),
-       fh_handle_filename (h), fh_handle_name (h));
-  assert (h->class == &dfm_r_class || h->class == &dfm_w_class);
+       handle_get_filename (h), handle_get_name (h));
+  assert (h->class == &dfm_r_class);
   if (ext->file.file)
     {
       fn_close_ext (&ext->file);
       free (ext->file.filename);
       ext->file.filename = NULL;
     }
-  free (ext->line);
+  ds_destroy (&ext->line);
+  ds_destroy (&ext->scratch);
   free (ext);
 }
 
-/* Initializes EXT properly as an inline data file. */
-static void
-open_inline_file (struct dfm_fhuser_ext *ext)
+/* Opens a file handle for reading as a data file.  Returns
+   nonzero only if successful. */
+int
+dfm_open_for_reading (struct file_handle *h)
 {
-  /* We want to indicate that the file is open, that we are not at
-     eof, and that another line needs to be read in.  */
-  ext->file.file = NULL;
-  ext->line = xmalloc (128);
-#if !PRODUCTION
-  strcpy (ext->line, _("<<Bug in dfm.c>>"));
-#endif
-  ext->len = strlen (ext->line);
-  ext->ptr = ext->line;
-  ext->size = 128;
-  ext->advance = 1;
-}
+  struct dfm_reader_ext *ext;
 
-/* Opens a file handle for reading as a data file. */
-static int
-open_file_r (struct file_handle *h)
-{
-  struct dfm_fhuser_ext ext;
+  if (h->class != NULL)
+    {
+      if (h->class == &dfm_r_class)
+        return 1;
+      else
+        {
+          msg (ME, _("Cannot read from file %s already opened for %s."),
+               handle_get_name (h), gettext (h->class->name));
+          return 0;
+        }
+    }
 
-  h->where.line_number = 0;
-  ext.file.file = NULL;
-  ext.line = NULL;
-  ext.len = 0;
-  ext.ptr = NULL;
-  ext.size = 0;
-  ext.advance = 0;
+  ext = xmalloc (sizeof *ext);
+  ext->where.filename = handle_get_filename (h);
+  ext->where.line_number = 0;
+  ext->file.file = NULL;
+  ds_init (&ext->line, 64);
+  ds_init (&ext->scratch, 0);
+  ext->flags = DFM_ADVANCE;
 
   msg (VM (1), _("%s: Opening data-file handle %s for reading."),
-       fh_handle_filename (h), fh_handle_name (h));
+       handle_get_filename (h), handle_get_name (h));
   
   assert (h != NULL);
-  if (h == inline_file)
-    {
-      char *s;
-
-      /* WTF can't this just be done with tokens?
-        Is this really a special case?
-        FIXME! */
-      do
-       {
-         char *cp;
-
-         if (!getl_read_line ())
-           {
-             msg (SE, _("BEGIN DATA expected."));
-             err_failure ();
-           }
-
-         /* Skip leading whitespace, separate out first word, so that
-            S points to a single word reduced to lowercase. */
-         s = ds_value (&getl_buf);
-         while (isspace ((unsigned char) *s))
-           s++;
-         for (cp = s; isalpha ((unsigned char) *cp); cp++)
-           *cp = tolower ((unsigned char) (*cp));
-         ds_truncate (&getl_buf, cp - s);
-       }
-      while (*s == '\0');
-
-      if (!lex_id_match_len ("begin", 5, s, strcspn (s, " \t\r\v\n")))
-       {
-         msg (SE, _("BEGIN DATA expected."));
-         err_cond_fail ();
-         lex_preprocess_line ();
-         return 0;
-       }
-      getl_prompt = GETL_PRPT_DATA;
-
-      open_inline_file (&ext);
-    }
-  else
+  if (h != inline_file)
     {
-      ext.file.filename = xstrdup (h->norm_fn);
-      ext.file.mode = "rb";
-      ext.file.file = NULL;
-      ext.file.sequence_no = NULL;
-      ext.file.param = NULL;
-      ext.file.postopen = NULL;
-      ext.file.preclose = NULL;
-      if (!fn_open_ext (&ext.file))
+      ext->file.filename = xstrdup (handle_get_filename (h));
+      ext->file.mode = "rb";
+      ext->file.file = NULL;
+      ext->file.sequence_no = NULL;
+      ext->file.param = NULL;
+      ext->file.postopen = NULL;
+      ext->file.preclose = NULL;
+      if (!fn_open_ext (&ext->file))
        {
-         msg (ME, _("An error occurred while opening \"%s\" for reading "
-              "as a data file: %s."), h->fn, strerror (errno));
-         err_cond_fail ();
-         return 0;
+         msg (ME, _("Could not open \"%s\" for reading "
+                     "as a data file: %s."),
+               handle_get_filename (h), strerror (errno));
+          goto error;
        }
     }
 
   h->class = &dfm_r_class;
-  h->ext = xmalloc (sizeof (struct dfm_fhuser_ext));
-  memcpy (h->ext, &ext, sizeof (struct dfm_fhuser_ext));
-
+  h->ext = ext;
   return 1;
-}
-
-/* Opens a file handle for writing as a data file. */
-static int
-open_file_w (struct file_handle *h)
-{
-  struct dfm_fhuser_ext ext;
-  
-  ext.file.file = NULL;
-  ext.line = NULL;
-  ext.len = 0;
-  ext.ptr = NULL;
-  ext.size = 0;
-  ext.advance = 0;
 
-  h->where.line_number = 0;
-
-  msg (VM (1), _("%s: Opening data-file handle %s for writing."),
-       fh_handle_filename (h), fh_handle_name (h));
-  
-  assert (h != NULL);
-  if (h == inline_file)
-    {
-      msg (ME, _("Cannot open the inline file for writing."));
-      err_cond_fail ();
-      return 0;
-    }
-
-  ext.file.filename = xstrdup (h->norm_fn);
-  ext.file.mode = "wb";
-  ext.file.file = NULL;
-  ext.file.sequence_no = NULL;
-  ext.file.param = NULL;
-  ext.file.postopen = NULL;
-  ext.file.preclose = NULL;
-      
-  if (!fn_open_ext (&ext.file))
-    {
-      msg (ME, _("An error occurred while opening \"%s\" for writing "
-          "as a data file: %s."), h->fn, strerror (errno));
-      err_cond_fail ();
-      return 0;
-    }
-
-  h->class = &dfm_w_class;
-  h->ext = xmalloc (sizeof (struct dfm_fhuser_ext));
-  memcpy (h->ext, &ext, sizeof (struct dfm_fhuser_ext));
-
-  return 1;
-}
-
-/* Ensures that the line buffer in file handle with extension EXT is
-   big enough to hold a line of length EXT->LEN characters not
-   including null terminator. */
-#define force_line_buffer_expansion()                          \
-       do                                                      \
-         {                                                     \
-           if (ext->len + 1 > ext->size)                       \
-             {                                                 \
-               ext->size = ext->len * 2;                       \
-               ext->line = xrealloc (ext->line, ext->size);    \
-             }                                                 \
-         }                                                     \
-       while (0)
-
-/* Counts the number of tabs in string STRING of length LEN. */
-static inline int
-count_tabs (char *s, size_t len)
-{
-  int n_tabs = 0;
-  
-  for (;;)
-    {
-      char *cp = memchr (s, '\t', len);
-      if (cp == NULL)
-       return n_tabs;
-      n_tabs++;
-      len -= cp - s + 1;
-      s = cp + 1;
-    }
-}
-   
-/* Converts all the tabs in H->EXT->LINE to an equivalent number of
-   spaces, if necessary. */
-static void
-tabs_to_spaces (struct file_handle *h)
-{
-  struct dfm_fhuser_ext *ext = h->ext;
-  
-  char *first_tab;             /* Location of first tab (if any). */
-  char *second_tab;            /* Location of second tab (if any). */
-  size_t orig_len;     /* Line length at function entry. */
-
-  /* If there aren't any tabs then there's nothing to do. */
-  first_tab = memchr (ext->line, '\t', ext->len);
-  if (first_tab == NULL)
-    return;
-  orig_len = ext->len;
-  
-  /* If there's just one tab then expand it inline.  Otherwise do a
-     full string copy to another buffer. */
-  second_tab = memchr (first_tab + 1, '\t',
-                      ext->len - (first_tab - ext->line + 1));
-  if (second_tab == NULL)
-    {
-      int n_spaces = 8 - (first_tab - ext->line) % 8;
-
-      ext->len += n_spaces - 1;
-
-      /* Expand the line if necessary, keeping the first_tab pointer
-         valid. */
-      {
-       size_t ofs = first_tab - ext->line;
-       force_line_buffer_expansion ();
-       first_tab = ext->line + ofs;
-      }
-      
-      memmove (first_tab + n_spaces, first_tab + 1,
-              orig_len - (first_tab - ext->line + 1));
-      memset (first_tab, ' ', n_spaces);
-    } else {
-      /* Make a local copy of original text. */
-      char *orig_line = local_alloc (ext->len + 1);
-      memcpy (orig_line, ext->line, ext->len);
-             
-      /* Allocate memory assuming we need to add 8 spaces for every tab. */
-      ext->len += 2 + count_tabs (second_tab + 1,
-                                 ext->len - (second_tab - ext->line + 1));
-      
-      /* Expand the line if necessary, keeping the first_tab pointer
-         valid. */
-      {
-       size_t ofs = first_tab - ext->line;
-       force_line_buffer_expansion ();
-       first_tab = ext->line + ofs;
-      }
-
-      /* Walk through orig_line, expanding tabs into ext->line. */
-      {
-       char *src_p = orig_line + (first_tab - ext->line);
-       char *dest_p = first_tab;
-
-       for (; src_p < orig_line + orig_len; src_p++)
-         {
-           /* Most characters simply pass through untouched. */
-           if (*src_p != '\t')
-             {
-               *dest_p++ = *src_p;
-               continue;
-             }
-
-           /* Tabs are expanded into an equivalent number of
-               spaces. */
-           {
-             int n_spaces = 8 - (dest_p - ext->line) % 8;
-
-             memset (dest_p, ' ', n_spaces);
-             dest_p += n_spaces;
-           }
-         }
-
-       /* Supply null terminator and actual string length. */
-       *dest_p = 0;
-       ext->len = dest_p - ext->line;
-      }
-
-      local_free (orig_line);
-    }
+ error:
+  err_cond_fail ();
+  free (ext);
+  return 0;
 }
 
 /* Reads a record from H->EXT->FILE into H->EXT->LINE, setting
@@ -358,71 +165,104 @@ tabs_to_spaces (struct file_handle *h)
 static void
 read_record (struct file_handle *h)
 {
-  struct dfm_fhuser_ext *ext = h->ext;
+  struct dfm_reader_ext *ext = get_reader (h);
 
   if (h == inline_file)
     {
+      if ((ext->flags & DFM_SAW_BEGIN_DATA) == 0)
+        {
+          char *s;
+
+          ext->flags |= DFM_SAW_BEGIN_DATA;
+
+          /* FIXME: WTF can't this just be done with tokens?
+             Is this really a special case? */
+          do
+            {
+              char *cp;
+
+              if (!getl_read_line ())
+                {
+                  msg (SE, _("BEGIN DATA expected."));
+                  err_failure ();
+                }
+
+              /* Skip leading whitespace, separate out first
+                 word, so that S points to a single word reduced
+                 to lowercase. */
+              s = ds_c_str (&getl_buf);
+              while (isspace ((unsigned char) *s))
+                s++;
+              for (cp = s; isalpha ((unsigned char) *cp); cp++)
+                *cp = tolower ((unsigned char) (*cp));
+              ds_truncate (&getl_buf, cp - s);
+            }
+          while (*s == '\0');
+
+          if (!lex_id_match_len ("begin", 5, s, strcspn (s, " \t\r\v\n")))
+            {
+              msg (SE, _("BEGIN DATA expected."));
+              lex_preprocess_line ();
+              goto eof;
+            }
+          getl_prompt = GETL_PRPT_DATA;
+        }
+      
       if (!getl_read_line ())
        {
          msg (SE, _("Unexpected end-of-file while reading data in BEGIN "
-              "DATA.  This probably indicates "
-              "a missing or misformatted END DATA command.  "
-              "END DATA must appear by itself on a single line "
-              "with exactly one space between words."));
+                     "DATA.  This probably indicates "
+                     "a missing or misformatted END DATA command.  "
+                     "END DATA must appear by itself on a single line "
+                     "with exactly one space between words."));
          err_failure ();
        }
 
-      h->where.line_number++;
+      ext->where.line_number++;
 
       if (ds_length (&getl_buf) >= 8
-         && !strncasecmp (ds_value (&getl_buf), "end data", 8))
+         && !strncasecmp (ds_c_str (&getl_buf), "end data", 8))
        {
-         lex_set_prog (ds_value (&getl_buf) + ds_length (&getl_buf));
+         lex_set_prog (ds_c_str (&getl_buf) + ds_length (&getl_buf));
          goto eof;
        }
 
-      ext->len = ds_length (&getl_buf);
-      force_line_buffer_expansion ();
-      strcpy (ext->line, ds_value (&getl_buf));
+      ds_replace (&ext->line, ds_c_str (&getl_buf));
     }
   else
     {
-      if (h->recform == FH_RF_VARIABLE)
+      if (handle_get_mode (h) == MODE_TEXT)
        {
-         /* PORTME: here you should adapt the routine to your
-            system's concept of a "line" of text. */
-         int read_len = getline (&ext->line, &ext->size, ext->file.file);
-
-         if (read_len == -1)
-           {
+          ds_clear (&ext->line);
+          if (!ds_gets (&ext->line, ext->file.file)) 
+            {
              if (ferror (ext->file.file))
                {
                  msg (ME, _("Error reading file %s: %s."),
-                      fh_handle_name (h), strerror (errno));
+                      handle_get_name (h), strerror (errno));
                  err_cond_fail ();
                }
              goto eof;
            }
-         ext->len = (size_t) read_len;
        }
-      else if (h->recform == FH_RF_FIXED)
+      else if (handle_get_mode (h) == MODE_BINARY)
        {
+          size_t record_width = handle_get_record_width (h);
          size_t amt;
 
-         if (ext->size < h->lrecl)
-           {
-             ext->size = h->lrecl;
-             ext->line = xmalloc (ext->size);
-           }
-         amt = fread (ext->line, 1, h->lrecl, ext->file.file);
-         if (h->lrecl != amt)
+          if (ds_length (&ext->line) < record_width) 
+            ds_rpad (&ext->line, record_width, 0);
+          
+         amt = fread (ds_c_str (&ext->line), 1, record_width,
+                       ext->file.file);
+         if (record_width != amt)
            {
              if (ferror (ext->file.file))
                msg (ME, _("Error reading file %s: %s."),
-                    fh_handle_name (h), strerror (errno));
+                    handle_get_name (h), strerror (errno));
              else if (amt != 0)
                msg (ME, _("%s: Partial record at end of file."),
-                    fh_handle_name (h));
+                    handle_get_name (h));
              else
                goto eof;
 
@@ -433,142 +273,249 @@ read_record (struct file_handle *h)
       else
        assert (0);
 
-      h->where.line_number++;
+      ext->where.line_number++;
     }
 
-  /* Strip trailing whitespace, I forget why.  But there's a good
-     reason, I'm sure.  I'm too scared to eliminate this code.  */
-  if (h->recform == FH_RF_VARIABLE)
-    {
-      while (ext->len && isspace ((unsigned char) ext->line[ext->len - 1]))
-       ext->len--;
-
-      /* Convert tabs to spaces. */
-      tabs_to_spaces (h);
-               
-      ext->ptr = ext->line;
-    }
+  ext->pos = 0;
   return;
 
 eof:
-  /*hit eof or an error, clean up everything. */
-  if (ext->line)
-    free (ext->line);
-  ext->size = 0;
-  ext->line = ext->ptr = NULL;
-  return;
+  /* Hit eof or an error, clean up everything. */
+  ext->flags |= DFM_EOF;
 }
-\f
-/* Public (high level). */
-
-/* Returns the current record in the file corresponding to HANDLE.
-   Opens files and reads records, etc., as necessary.  Sets *LEN to
-   the length of the line.  The line returned is not null-terminated.
-   Returns NULL at end of file.  Calls fail() on attempt to read past
-   end of file.  */
-char *
-dfm_get_record (struct file_handle *h, int *len)
-{
-  assert (h != NULL);
 
-  if (h->class == NULL)
-    {
-      if (!open_file_r (h))
-       return NULL;
-      read_record (h);
-    }
-  else if (h->class != &dfm_r_class)
+/* Returns nonzero if end of file has been reached on HANDLE.
+   Reads forward in HANDLE's file, if necessary to tell. */
+int
+dfm_eof (struct file_handle *h) 
+{
+  struct dfm_reader_ext *ext = get_reader (h);
+  if (ext->flags & DFM_ADVANCE)
     {
-      msg (ME, _("Cannot read from file %s already opened for %s."),
-          fh_handle_name (h), gettext (h->class->name));
-      goto lossage;
+      ext->flags &= ~DFM_ADVANCE;
+      if ((ext->flags & DFM_EOF) == 0)
+        read_record (h);
+      else
+        {
+          msg (SE, _("Attempt to read beyond end-of-file on file %s."),
+               handle_get_name (h));
+          err_cond_fail ();
+        }
     }
-  else
-    {
-      struct dfm_fhuser_ext *ext = h->ext;
 
-      if (ext->advance)
-       {
-         if (ext->line)
-           read_record (h);
-         else
-           {
-             msg (SE, _("Attempt to read beyond end-of-file on file %s."),
-                  fh_handle_name (h));
-             goto lossage;
-           }
-       }
-    }
+  return (ext->flags & DFM_EOF) != 0;
+}
 
-  {
-    struct dfm_fhuser_ext *ext = h->ext;
-
-    if (ext)
-      {
-       ext->advance = 0;
-       if (len)
-         *len = ext->len - (ext->ptr - ext->line);
-       return ext->ptr;
-      }
-  }
-
-  return NULL;
-
-lossage:
-  /* Come here on reading beyond eof or reading from a file already
-     open for something else. */
-  err_cond_fail ();
+/* Returns the current record in the file corresponding to
+   HANDLE.  Aborts if reading from the file is necessary or at
+   end of file, so call dfm_eof() first.  Sets *LINE to the line,
+   which is not null-terminated.  The caller must not free or
+   modify the returned string.  */
+void
+dfm_get_record (struct file_handle *h, struct len_string *line)
+{
+  struct dfm_reader_ext *ext = get_reader (h);
+  assert ((ext->flags & DFM_ADVANCE) == 0);
+  assert ((ext->flags & DFM_EOF) == 0);
+  assert (ext->pos <= ds_length (&ext->line));
+
+  line->string = ds_data (&ext->line) + ext->pos;
+  line->length = ds_length (&ext->line) - ext->pos;
+}
+
+/* Expands tabs in the current line into the equivalent number of
+   spaces, if appropriate for this kind of file.  Aborts if
+   reading from the file is necessary or at end of file, so call
+   dfm_eof() first.*/
+void
+dfm_expand_tabs (struct file_handle *h) 
+{
+  struct dfm_reader_ext *ext = get_reader (h);
+  struct string temp;
+  size_t ofs, new_pos, tab_width;
+
+  assert ((ext->flags & DFM_ADVANCE) == 0);
+  assert ((ext->flags & DFM_EOF) == 0);
+  assert (ext->pos <= ds_length (&ext->line));
+
+  if (ext->flags & DFM_TABS_EXPANDED)
+    return;
+  ext->flags |= DFM_TABS_EXPANDED;
+
+  if (handle_get_mode (h) == MODE_BINARY
+      || handle_get_tab_width (h) == 0
+      || memchr (ds_c_str (&ext->line), '\t', ds_length (&ext->line)) == NULL)
+    return;
+
+  /* Expand tabs from ext->line into ext->scratch, and figure out
+     new value for ext->pos. */
+  tab_width = handle_get_tab_width (h);
+  ds_clear (&ext->scratch);
+  new_pos = 0;
+  for (ofs = 0; ofs < ds_length (&ext->line); ofs++)
+    {
+      unsigned char c;
+      
+      if (ofs == ext->pos)
+        new_pos = ds_length (&ext->scratch);
+
+      c = ds_c_str (&ext->line)[ofs];
+      if (c != '\t')
+        ds_putc (&ext->scratch, c);
+      else 
+        {
+          do
+            ds_putc (&ext->scratch, ' ');
+          while (ds_length (&ext->scratch) % tab_width != 0);
+        }
+    }
 
-  return NULL;
+  /* Swap ext->line and ext->scratch and set new ext->pos. */
+  temp = ext->line;
+  ext->line = ext->scratch;
+  ext->scratch = temp;
+  ext->pos = new_pos;
 }
 
 /* Causes dfm_get_record() to read in the next record the next time it
    is executed on file HANDLE. */
 void
-dfm_fwd_record (struct file_handle *h)
+dfm_forward_record (struct file_handle *h)
 {
-  struct dfm_fhuser_ext *ext = h->ext;
+  struct dfm_reader_ext *ext = get_reader (h);
+  ext->flags |= DFM_ADVANCE;
+}
 
-  assert (h->class == &dfm_r_class);
-  ext->advance = 1;
+/* Cancels the effect of any previous dfm_fwd_record() executed
+   on file HANDLE.  Sets the current line to begin in the 1-based
+   column COLUMN.  */
+void
+dfm_reread_record (struct file_handle *h, size_t column)
+{
+  struct dfm_reader_ext *ext = get_reader (h);
+  ext->flags &= ~DFM_ADVANCE;
+  if (column < 1)
+    ext->pos = 0;
+  else if (column > ds_length (&ext->line))
+    ext->pos = ds_length (&ext->line);
+  else
+    ext->pos = column - 1;
 }
 
-/* Cancels the effect of any previous dfm_fwd_record() executed on
-   file HANDLE.  Sets the current line to begin in the 1-based column
-   COLUMN, as with dfm_set_record but based on a column number instead
-   of a character pointer. */
+/* Sets the current line to begin COLUMNS characters following
+   the current start. */
 void
-dfm_bkwd_record (struct file_handle *h, int column)
+dfm_forward_columns (struct file_handle *h, size_t columns)
 {
-  struct dfm_fhuser_ext *ext = h->ext;
+  struct dfm_reader_ext *ext = get_reader (h);
+  dfm_reread_record (h, (ext->pos + 1) + columns);
+}
 
-  assert (h->class == &dfm_r_class);
-  ext->advance = 0;
-  ext->ptr = ext->line + min ((int) ext->len + 1, column) - 1;
+/* Returns the 1-based column to which the line pointer in HANDLE
+   is set.  Unless dfm_reread_record() or dfm_forward_columns()
+   have been called, this is 1. */
+size_t
+dfm_column_start (struct file_handle *h)
+{
+  struct dfm_reader_ext *ext = get_reader (h);
+  return ext->pos + 1;
 }
 
-/* Sets the current line in HANDLE to NEW_LINE, which must point
-   somewhere in the line last returned by dfm_get_record().  Used by
-   DATA LIST FREE to strip the leading portion off the current line.  */
+/* Pushes the filename and line number on the fn/ln stack. */
 void
-dfm_set_record (struct file_handle *h, char *new_line)
+dfm_push (struct file_handle *h)
 {
-  struct dfm_fhuser_ext *ext = h->ext;
+  struct dfm_reader_ext *ext = get_reader (h);
+  if (h != inline_file)
+    err_push_file_locator (&ext->where);
+}
 
-  assert (h->class == &dfm_r_class);
-  ext->ptr = new_line;
+/* Pops the filename and line number from the fn/ln stack. */
+void
+dfm_pop (struct file_handle *h)
+{
+  struct dfm_reader_ext *ext = get_reader (h);
+  if (h != inline_file)
+    err_pop_file_locator (&ext->where);
 }
 
-/* Returns the 0-based current column to which the line pointer in
-   HANDLE is set.  Unless dfm_set_record() or dfm_bkwd_record() have
-   been called, this is 0. */
+/* DFM reader class. */
+static struct fh_ext_class dfm_r_class =
+{
+  1,
+  N_("reading as a data file"),
+  close_reader,
+};
+\f
+/* file_handle extension structure. */
+struct dfm_writer_ext
+  {
+    struct file_ext file;      /* Associated file. */
+    struct file_locator where;  /* Current location in data file. */
+    char *bounce;               /* Bounce buffer for fixed-size fields. */
+  };
+
+static struct fh_ext_class dfm_w_class;
+
+/* Opens a file handle for writing as a data file. */
 int
-dfm_get_cur_col (struct file_handle *h)
+dfm_open_for_writing (struct file_handle *h)
 {
-  struct dfm_fhuser_ext *ext = h->ext;
+  struct dfm_writer_ext *ext;
+  
+  if (h->class != NULL)
+    {
+      if (h->class == &dfm_w_class)
+        return 1;
+      else
+        {
+          msg (ME, _("Cannot write to file %s already opened for %s."),
+               handle_get_name (h), gettext (h->class->name));
+          err_cond_fail ();
+          return 0;
+        }
+    }
 
-  assert (h->class == &dfm_r_class);
-  return ext->ptr - ext->line;
+  ext = xmalloc (sizeof *ext);
+  ext->where.filename = handle_get_filename (h);
+  ext->where.line_number = 0;
+  ext->file.file = NULL;
+  ext->bounce = NULL;
+
+  msg (VM (1), _("%s: Opening data-file handle %s for writing."),
+       handle_get_filename (h), handle_get_name (h));
+  
+  assert (h != NULL);
+  if (h == inline_file)
+    {
+      msg (ME, _("Cannot open the inline file for writing."));
+      goto error;
+    }
+
+  ext->file.filename = xstrdup (handle_get_filename (h));
+  ext->file.mode = "wb";
+  ext->file.file = NULL;
+  ext->file.sequence_no = NULL;
+  ext->file.param = NULL;
+  ext->file.postopen = NULL;
+  ext->file.preclose = NULL;
+      
+  if (!fn_open_ext (&ext->file))
+    {
+      msg (ME, _("An error occurred while opening \"%s\" for writing "
+                 "as a data file: %s."),
+           handle_get_filename (h), strerror (errno));
+      goto error;
+    }
+
+  h->class = &dfm_w_class;
+  h->ext = ext;
+  return 1;
+
+ error:
+  free (ext);
+  err_cond_fail ();
+  return 0;
 }
 
 /* Writes record REC having length LEN to the file corresponding to
@@ -577,67 +524,63 @@ dfm_get_cur_col (struct file_handle *h)
 int
 dfm_put_record (struct file_handle *h, const char *rec, size_t len)
 {
-  char *ptr;
-  size_t amt;
-
-  if (h->class == NULL)
-    {
-      if (!open_file_w (h))
-       return 0;
-    }
-  else if (h->class != &dfm_w_class)
-    {
-      msg (ME, _("Cannot write to file %s already opened for %s."),
-          fh_handle_name (h), gettext (h->class->name));
-      err_cond_fail ();
-      return 0;
-    }
+  struct dfm_writer_ext *ext;
 
-  if (h->recform == FH_RF_FIXED && len < h->lrecl)
-    {
-      int ch;
+  assert (h != NULL);
+  assert (h->class == &dfm_w_class);
+  assert (h->ext != NULL);
 
-      amt = h->lrecl;
-      ptr = local_alloc (amt);
-      memcpy (ptr, rec, len);
-      ch = h->mode == FH_MD_CHARACTER ? ' ' : 0;
-      memset (&ptr[len], ch, amt - len);
-    }
-  else
+  ext = h->ext;
+  if (handle_get_mode (h) == MODE_BINARY && len < handle_get_record_width (h))
     {
-      ptr = (char *) rec;
-      amt = len;
+      size_t rec_width = handle_get_record_width (h);
+      if (ext->bounce == NULL)
+        ext->bounce = xmalloc (rec_width);
+      memcpy (ext->bounce, rec, len);
+      memset (&ext->bounce[len], 0, rec_width - len);
+      rec = ext->bounce;
+      len = rec_width;
     }
 
-  if (1 != fwrite (ptr, amt, 1, ((struct dfm_fhuser_ext *) h->ext)->file.file))
+  if (fwrite (rec, len, 1, ext->file.file) != 1)
     {
-      msg (ME, _("Error writing file %s: %s."), fh_handle_name (h),
-          strerror (errno));
+      msg (ME, _("Error writing file %s: %s."),
+           handle_get_name (h), strerror (errno));
       err_cond_fail ();
       return 0;
     }
 
-  if (ptr != rec)
-    local_free (ptr);
-
   return 1;
 }
 
-/* Pushes the filename and line number on the fn/ln stack. */
-void
-dfm_push (struct file_handle *h)
+/* Closes file handle H opened by dfm_open_for_writing(). */
+static void
+close_writer (struct file_handle *h)
 {
-  if (h != inline_file)
-    err_push_file_locator (&h->where);
+  struct dfm_writer_ext *ext;
+
+  assert (h->class == &dfm_w_class);
+  ext = h->ext;
+
+  msg (VM (2), _("%s: Closing data-file handle %s."),
+       handle_get_filename (h), handle_get_name (h));
+  if (ext->file.file)
+    {
+      fn_close_ext (&ext->file);
+      free (ext->file.filename);
+      ext->file.filename = NULL;
+    }
+  free (ext->bounce);
+  free (ext);
 }
 
-/* Pops the filename and line number from the fn/ln stack. */
-void
-dfm_pop (struct file_handle *h)
+/* DFM writer class. */
+static struct fh_ext_class dfm_w_class =
 {
-  if (h != inline_file)
-    err_pop_file_locator (&h->where);
-}
+  2,
+  N_("writing as a data file"),
+  close_writer,
+};
 \f
 /* BEGIN DATA...END DATA procedure. */
 
@@ -645,7 +588,7 @@ dfm_pop (struct file_handle *h)
 int
 cmd_begin_data (void)
 {
-  struct dfm_fhuser_ext *ext;
+  struct dfm_reader_ext *ext;
 
   /* FIXME: figure out the *exact* conditions, not these really
      lenient conditions. */
@@ -654,45 +597,30 @@ cmd_begin_data (void)
       || case_source_is_class (vfm_source, &sort_source_class))
     {
       msg (SE, _("This command is not valid here since the current "
-          "input program does not access the inline file."));
+                 "input program does not access the inline file."));
       err_cond_fail ();
       return CMD_FAILURE;
     }
 
   /* Initialize inline_file. */
   msg (VM (1), _("inline file: Opening for reading."));
-  inline_file->class = &dfm_r_class;
-  inline_file->ext = xmalloc (sizeof (struct dfm_fhuser_ext));
-  open_inline_file (inline_file->ext);
+  dfm_open_for_reading (inline_file);
+  ext = inline_file->ext;
+  ext->flags |= DFM_SAW_BEGIN_DATA;
 
   /* We don't actually read from the inline file.  The input procedure
      is what reads from it. */
   getl_prompt = GETL_PRPT_DATA;
   procedure (NULL, NULL);
-
+  
   ext = inline_file->ext;
-
-  if (ext && ext->line)
+  if (ext && (ext->flags & DFM_EOF) == 0)
     {
       msg (MW, _("Skipping remaining inline data."));
-      for (read_record (inline_file); ext->line; read_record (inline_file))
-       ;
+      while ((ext->flags & DFM_EOF) == 0)
+        read_record (inline_file);
     }
   assert (inline_file->ext == NULL);
 
   return CMD_SUCCESS;
 }
-
-static struct fh_ext_class dfm_r_class =
-{
-  1,
-  N_("reading as a data file"),
-  dfm_close,
-};
-
-static struct fh_ext_class dfm_w_class =
-{
-  2,
-  N_("writing as a data file"),
-  dfm_close,
-};