9905b6b3e25f8cc8c688a1bde3b268dbf2b61457
[pspp-builds.git] / src / dfm.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18    02111-1307, USA. */
19
20 #include <config.h>
21 #include <assert.h>
22 #include "dfm.h"
23 #include <ctype.h>
24 #include <errno.h>
25 #include <stdlib.h>
26 #include "alloc.h"
27 #include "command.h"
28 #include "error.h"
29 #include "file-handle.h"
30 #include "filename.h"
31 #include "getline.h"
32 #include "lexer.h"
33 #include "misc.h"
34 #include "str.h"
35 #include "vfm.h"
36
37 #include "debug-print.h"
38
39 /* file_handle extension structure. */
40 struct dfm_fhuser_ext
41   {
42     struct file_ext file;       /* Associated file. */
43
44     char *line;                 /* Current line, not null-terminated. */
45     size_t len;                 /* Length of line. */
46
47     char *ptr;                  /* Pointer into line that is returned by
48                                    dfm_get_record(). */
49     size_t size;                /* Number of bytes allocated for line. */
50     int advance;                /* Nonzero=dfm_get_record() reads a new
51                                    record; otherwise returns current record. */
52   };
53
54 /* These are defined at the end of this file. */
55 static struct fh_ext_class dfm_r_class;
56 static struct fh_ext_class dfm_w_class;
57
58 static void read_record (struct file_handle *h);
59 \f
60 /* Internal (low level). */
61
62 /* Closes the file handle H which was opened by open_file_r() or
63    open_file_w(). */
64 static void
65 dfm_close (struct file_handle *h)
66 {
67   struct dfm_fhuser_ext *ext = h->ext;
68
69   /* Skip any remaining data on the inline file. */
70   if (h == inline_file)
71     while (ext->line != NULL)
72       read_record (h);
73       
74   msg (VM (2), _("%s: Closing data-file handle %s."),
75        fh_handle_filename (h), fh_handle_name (h));
76   assert (h->class == &dfm_r_class || h->class == &dfm_w_class);
77   if (ext->file.file)
78     {
79       fn_close_ext (&ext->file);
80       free (ext->file.filename);
81       ext->file.filename = NULL;
82     }
83   free (ext->line);
84   free (ext);
85 }
86
87 /* Initializes EXT properly as an inline data file. */
88 static void
89 open_inline_file (struct dfm_fhuser_ext *ext)
90 {
91   /* We want to indicate that the file is open, that we are not at
92      eof, and that another line needs to be read in.  */
93   ext->file.file = NULL;
94   ext->line = xmalloc (128);
95 #if !PRODUCTION
96   strcpy (ext->line, _("<<Bug in dfm.c>>"));
97 #endif
98   ext->len = strlen (ext->line);
99   ext->ptr = ext->line;
100   ext->size = 128;
101   ext->advance = 1;
102 }
103
104 /* Opens a file handle for reading as a data file. */
105 static int
106 open_file_r (struct file_handle *h)
107 {
108   struct dfm_fhuser_ext ext;
109
110   h->where.line_number = 0;
111   ext.file.file = NULL;
112   ext.line = NULL;
113   ext.len = 0;
114   ext.ptr = NULL;
115   ext.size = 0;
116   ext.advance = 0;
117
118   msg (VM (1), _("%s: Opening data-file handle %s for reading."),
119        fh_handle_filename (h), fh_handle_name (h));
120   
121   assert (h != NULL);
122   if (h == inline_file)
123     {
124       char *s;
125
126       /* WTF can't this just be done with tokens?
127          Is this really a special case?
128          FIXME! */
129       do
130         {
131           char *cp;
132
133           if (!getl_read_line ())
134             {
135               msg (SE, _("BEGIN DATA expected."));
136               err_failure ();
137             }
138
139           /* Skip leading whitespace, separate out first word, so that
140              S points to a single word reduced to lowercase. */
141           s = ds_value (&getl_buf);
142           while (isspace ((unsigned char) *s))
143             s++;
144           for (cp = s; isalpha ((unsigned char) *cp); cp++)
145             *cp = tolower ((unsigned char) (*cp));
146           ds_truncate (&getl_buf, cp - s);
147         }
148       while (*s == '\0');
149
150       if (!lex_id_match_len ("begin", 5, s, strcspn (s, " \t\r\v\n")))
151         {
152           msg (SE, _("BEGIN DATA expected."));
153           err_cond_fail ();
154           lex_preprocess_line ();
155           return 0;
156         }
157       getl_prompt = GETL_PRPT_DATA;
158
159       open_inline_file (&ext);
160     }
161   else
162     {
163       ext.file.filename = xstrdup (h->norm_fn);
164       ext.file.mode = "rb";
165       ext.file.file = NULL;
166       ext.file.sequence_no = NULL;
167       ext.file.param = NULL;
168       ext.file.postopen = NULL;
169       ext.file.preclose = NULL;
170       if (!fn_open_ext (&ext.file))
171         {
172           msg (ME, _("An error occurred while opening \"%s\" for reading "
173                "as a data file: %s."), h->fn, strerror (errno));
174           err_cond_fail ();
175           return 0;
176         }
177     }
178
179   h->class = &dfm_r_class;
180   h->ext = xmalloc (sizeof (struct dfm_fhuser_ext));
181   memcpy (h->ext, &ext, sizeof (struct dfm_fhuser_ext));
182
183   return 1;
184 }
185
186 /* Opens a file handle for writing as a data file. */
187 static int
188 open_file_w (struct file_handle *h)
189 {
190   struct dfm_fhuser_ext ext;
191   
192   ext.file.file = NULL;
193   ext.line = NULL;
194   ext.len = 0;
195   ext.ptr = NULL;
196   ext.size = 0;
197   ext.advance = 0;
198
199   h->where.line_number = 0;
200
201   msg (VM (1), _("%s: Opening data-file handle %s for writing."),
202        fh_handle_filename (h), fh_handle_name (h));
203   
204   assert (h != NULL);
205   if (h == inline_file)
206     {
207       msg (ME, _("Cannot open the inline file for writing."));
208       err_cond_fail ();
209       return 0;
210     }
211
212   ext.file.filename = xstrdup (h->norm_fn);
213   ext.file.mode = "wb";
214   ext.file.file = NULL;
215   ext.file.sequence_no = NULL;
216   ext.file.param = NULL;
217   ext.file.postopen = NULL;
218   ext.file.preclose = NULL;
219       
220   if (!fn_open_ext (&ext.file))
221     {
222       msg (ME, _("An error occurred while opening \"%s\" for writing "
223            "as a data file: %s."), h->fn, strerror (errno));
224       err_cond_fail ();
225       return 0;
226     }
227
228   h->class = &dfm_w_class;
229   h->ext = xmalloc (sizeof (struct dfm_fhuser_ext));
230   memcpy (h->ext, &ext, sizeof (struct dfm_fhuser_ext));
231
232   return 1;
233 }
234
235 /* Ensures that the line buffer in file handle with extension EXT is
236    big enough to hold a line of length EXT->LEN characters not
237    including null terminator. */
238 #define force_line_buffer_expansion()                           \
239         do                                                      \
240           {                                                     \
241             if (ext->len + 1 > ext->size)                       \
242               {                                                 \
243                 ext->size = ext->len * 2;                       \
244                 ext->line = xrealloc (ext->line, ext->size);    \
245               }                                                 \
246           }                                                     \
247         while (0)
248
249 /* Counts the number of tabs in string STRING of length LEN. */
250 static inline int
251 count_tabs (char *s, size_t len)
252 {
253   int n_tabs = 0;
254   
255   for (;;)
256     {
257       char *cp = memchr (s, '\t', len);
258       if (cp == NULL)
259         return n_tabs;
260       n_tabs++;
261       len -= cp - s + 1;
262       s = cp + 1;
263     }
264 }
265    
266 /* Converts all the tabs in H->EXT->LINE to an equivalent number of
267    spaces, if necessary. */
268 static void
269 tabs_to_spaces (struct file_handle *h)
270 {
271   struct dfm_fhuser_ext *ext = h->ext;
272   
273   char *first_tab;              /* Location of first tab (if any). */
274   char *second_tab;             /* Location of second tab (if any). */
275   size_t orig_len;      /* Line length at function entry. */
276
277   /* If there aren't any tabs then there's nothing to do. */
278   first_tab = memchr (ext->line, '\t', ext->len);
279   if (first_tab == NULL)
280     return;
281   orig_len = ext->len;
282   
283   /* If there's just one tab then expand it inline.  Otherwise do a
284      full string copy to another buffer. */
285   second_tab = memchr (first_tab + 1, '\t',
286                        ext->len - (first_tab - ext->line + 1));
287   if (second_tab == NULL)
288     {
289       int n_spaces = 8 - (first_tab - ext->line) % 8;
290
291       ext->len += n_spaces - 1;
292
293       /* Expand the line if necessary, keeping the first_tab pointer
294          valid. */
295       {
296         size_t ofs = first_tab - ext->line;
297         force_line_buffer_expansion ();
298         first_tab = ext->line + ofs;
299       }
300       
301       memmove (first_tab + n_spaces, first_tab + 1,
302                orig_len - (first_tab - ext->line + 1));
303       memset (first_tab, ' ', n_spaces);
304     } else {
305       /* Make a local copy of original text. */
306       char *orig_line = local_alloc (ext->len + 1);
307       memcpy (orig_line, ext->line, ext->len);
308               
309       /* Allocate memory assuming we need to add 8 spaces for every tab. */
310       ext->len += 2 + count_tabs (second_tab + 1,
311                                   ext->len - (second_tab - ext->line + 1));
312       
313       /* Expand the line if necessary, keeping the first_tab pointer
314          valid. */
315       {
316         size_t ofs = first_tab - ext->line;
317         force_line_buffer_expansion ();
318         first_tab = ext->line + ofs;
319       }
320
321       /* Walk through orig_line, expanding tabs into ext->line. */
322       {
323         char *src_p = orig_line + (first_tab - ext->line);
324         char *dest_p = first_tab;
325
326         for (; src_p < orig_line + orig_len; src_p++)
327           {
328             /* Most characters simply pass through untouched. */
329             if (*src_p != '\t')
330               {
331                 *dest_p++ = *src_p;
332                 continue;
333               }
334
335             /* Tabs are expanded into an equivalent number of
336                spaces. */
337             {
338               int n_spaces = 8 - (dest_p - ext->line) % 8;
339
340               memset (dest_p, ' ', n_spaces);
341               dest_p += n_spaces;
342             }
343           }
344
345         /* Supply null terminator and actual string length. */
346         *dest_p = 0;
347         ext->len = dest_p - ext->line;
348       }
349
350       local_free (orig_line);
351     }
352 }
353
354 /* Reads a record from H->EXT->FILE into H->EXT->LINE, setting
355    H->EXT->PTR to H->EXT->LINE, and setting H->EXT-LEN to the length
356    of the line.  The line is not null-terminated.  If an error occurs
357    or end-of-file is encountered, H->EXT->LINE is set to NULL. */
358 static void
359 read_record (struct file_handle *h)
360 {
361   struct dfm_fhuser_ext *ext = h->ext;
362
363   if (h == inline_file)
364     {
365       if (!getl_read_line ())
366         {
367           msg (SE, _("Unexpected end-of-file while reading data in BEGIN "
368                "DATA.  This probably indicates "
369                "a missing or misformatted END DATA command.  "
370                "END DATA must appear by itself on a single line "
371                "with exactly one space between words."));
372           err_failure ();
373         }
374
375       h->where.line_number++;
376
377       if (ds_length (&getl_buf) >= 8
378           && !strncasecmp (ds_value (&getl_buf), "end data", 8))
379         {
380           lex_set_prog (ds_value (&getl_buf) + ds_length (&getl_buf));
381           goto eof;
382         }
383
384       ext->len = ds_length (&getl_buf);
385       force_line_buffer_expansion ();
386       strcpy (ext->line, ds_value (&getl_buf));
387     }
388   else
389     {
390       if (h->recform == FH_RF_VARIABLE)
391         {
392           /* PORTME: here you should adapt the routine to your
393              system's concept of a "line" of text. */
394           int read_len = getline (&ext->line, &ext->size, ext->file.file);
395
396           if (read_len == -1)
397             {
398               if (ferror (ext->file.file))
399                 {
400                   msg (ME, _("Error reading file %s: %s."),
401                        fh_handle_name (h), strerror (errno));
402                   err_cond_fail ();
403                 }
404               goto eof;
405             }
406           ext->len = (size_t) read_len;
407         }
408       else if (h->recform == FH_RF_FIXED)
409         {
410           size_t amt;
411
412           if (ext->size < h->lrecl)
413             {
414               ext->size = h->lrecl;
415               ext->line = xmalloc (ext->size);
416             }
417           amt = fread (ext->line, 1, h->lrecl, ext->file.file);
418           if (h->lrecl != amt)
419             {
420               if (ferror (ext->file.file))
421                 msg (ME, _("Error reading file %s: %s."),
422                      fh_handle_name (h), strerror (errno));
423               else if (amt != 0)
424                 msg (ME, _("%s: Partial record at end of file."),
425                      fh_handle_name (h));
426               else
427                 goto eof;
428
429               err_cond_fail ();
430               goto eof;
431             }
432         }
433       else
434         assert (0);
435
436       h->where.line_number++;
437     }
438
439   /* Strip trailing whitespace, I forget why.  But there's a good
440      reason, I'm sure.  I'm too scared to eliminate this code.  */
441   if (h->recform == FH_RF_VARIABLE)
442     {
443       while (ext->len && isspace ((unsigned char) ext->line[ext->len - 1]))
444         ext->len--;
445
446       /* Convert tabs to spaces. */
447       tabs_to_spaces (h);
448                 
449       ext->ptr = ext->line;
450     }
451   return;
452
453 eof:
454   /*hit eof or an error, clean up everything. */
455   if (ext->line)
456     free (ext->line);
457   ext->size = 0;
458   ext->line = ext->ptr = NULL;
459   return;
460 }
461 \f
462 /* Public (high level). */
463
464 /* Returns the current record in the file corresponding to HANDLE.
465    Opens files and reads records, etc., as necessary.  Sets *LEN to
466    the length of the line.  The line returned is not null-terminated.
467    Returns NULL at end of file.  Calls fail() on attempt to read past
468    end of file.  */
469 char *
470 dfm_get_record (struct file_handle *h, int *len)
471 {
472   assert (h != NULL);
473
474   if (h->class == NULL)
475     {
476       if (!open_file_r (h))
477         return NULL;
478       read_record (h);
479     }
480   else if (h->class != &dfm_r_class)
481     {
482       msg (ME, _("Cannot read from file %s already opened for %s."),
483            fh_handle_name (h), gettext (h->class->name));
484       goto lossage;
485     }
486   else
487     {
488       struct dfm_fhuser_ext *ext = h->ext;
489
490       if (ext->advance)
491         {
492           if (ext->line)
493             read_record (h);
494           else
495             {
496               msg (SE, _("Attempt to read beyond end-of-file on file %s."),
497                    fh_handle_name (h));
498               goto lossage;
499             }
500         }
501     }
502
503   {
504     struct dfm_fhuser_ext *ext = h->ext;
505
506     if (ext)
507       {
508         ext->advance = 0;
509         if (len)
510           *len = ext->len - (ext->ptr - ext->line);
511         return ext->ptr;
512       }
513   }
514
515   return NULL;
516
517 lossage:
518   /* Come here on reading beyond eof or reading from a file already
519      open for something else. */
520   err_cond_fail ();
521
522   return NULL;
523 }
524
525 /* Causes dfm_get_record() to read in the next record the next time it
526    is executed on file HANDLE. */
527 void
528 dfm_fwd_record (struct file_handle *h)
529 {
530   struct dfm_fhuser_ext *ext = h->ext;
531
532   assert (h->class == &dfm_r_class);
533   ext->advance = 1;
534 }
535
536 /* Cancels the effect of any previous dfm_fwd_record() executed on
537    file HANDLE.  Sets the current line to begin in the 1-based column
538    COLUMN, as with dfm_set_record but based on a column number instead
539    of a character pointer. */
540 void
541 dfm_bkwd_record (struct file_handle *h, int column)
542 {
543   struct dfm_fhuser_ext *ext = h->ext;
544
545   assert (h->class == &dfm_r_class);
546   ext->advance = 0;
547   ext->ptr = ext->line + min ((int) ext->len + 1, column) - 1;
548 }
549
550 /* Sets the current line in HANDLE to NEW_LINE, which must point
551    somewhere in the line last returned by dfm_get_record().  Used by
552    DATA LIST FREE to strip the leading portion off the current line.  */
553 void
554 dfm_set_record (struct file_handle *h, char *new_line)
555 {
556   struct dfm_fhuser_ext *ext = h->ext;
557
558   assert (h->class == &dfm_r_class);
559   ext->ptr = new_line;
560 }
561
562 /* Returns the 0-based current column to which the line pointer in
563    HANDLE is set.  Unless dfm_set_record() or dfm_bkwd_record() have
564    been called, this is 0. */
565 int
566 dfm_get_cur_col (struct file_handle *h)
567 {
568   struct dfm_fhuser_ext *ext = h->ext;
569
570   assert (h->class == &dfm_r_class);
571   return ext->ptr - ext->line;
572 }
573
574 /* Writes record REC having length LEN to the file corresponding to
575    HANDLE.  REC is not null-terminated.  Returns nonzero on success,
576    zero on failure. */
577 int
578 dfm_put_record (struct file_handle *h, const char *rec, size_t len)
579 {
580   char *ptr;
581   size_t amt;
582
583   if (h->class == NULL)
584     {
585       if (!open_file_w (h))
586         return 0;
587     }
588   else if (h->class != &dfm_w_class)
589     {
590       msg (ME, _("Cannot write to file %s already opened for %s."),
591            fh_handle_name (h), gettext (h->class->name));
592       err_cond_fail ();
593       return 0;
594     }
595
596   if (h->recform == FH_RF_FIXED && len < h->lrecl)
597     {
598       int ch;
599
600       amt = h->lrecl;
601       ptr = local_alloc (amt);
602       memcpy (ptr, rec, len);
603       ch = h->mode == FH_MD_CHARACTER ? ' ' : 0;
604       memset (&ptr[len], ch, amt - len);
605     }
606   else
607     {
608       ptr = (char *) rec;
609       amt = len;
610     }
611
612   if (1 != fwrite (ptr, amt, 1, ((struct dfm_fhuser_ext *) h->ext)->file.file))
613     {
614       msg (ME, _("Error writing file %s: %s."), fh_handle_name (h),
615            strerror (errno));
616       err_cond_fail ();
617       return 0;
618     }
619
620   if (ptr != rec)
621     local_free (ptr);
622
623   return 1;
624 }
625
626 /* Pushes the filename and line number on the fn/ln stack. */
627 void
628 dfm_push (struct file_handle *h)
629 {
630   if (h != inline_file)
631     err_push_file_locator (&h->where);
632 }
633
634 /* Pops the filename and line number from the fn/ln stack. */
635 void
636 dfm_pop (struct file_handle *h)
637 {
638   if (h != inline_file)
639     err_pop_file_locator (&h->where);
640 }
641 \f
642 /* BEGIN DATA...END DATA procedure. */
643
644 /* Perform BEGIN DATA...END DATA as a procedure in itself. */
645 int
646 cmd_begin_data (void)
647 {
648   struct dfm_fhuser_ext *ext;
649
650   /* FIXME: figure out the *exact* conditions, not these really
651      lenient conditions. */
652   if (vfm_source == NULL
653       || vfm_source == &vfm_memory_stream
654       || vfm_source == &vfm_disk_stream
655       || vfm_source == &sort_stream)
656     {
657       msg (SE, _("This command is not valid here since the current "
658            "input program does not access the inline file."));
659       err_cond_fail ();
660       return CMD_FAILURE;
661     }
662
663   /* Initialize inline_file. */
664   msg (VM (1), _("inline file: Opening for reading."));
665   inline_file->class = &dfm_r_class;
666   inline_file->ext = xmalloc (sizeof (struct dfm_fhuser_ext));
667   open_inline_file (inline_file->ext);
668
669   /* We don't actually read from the inline file.  The input procedure
670      is what reads from it. */
671   getl_prompt = GETL_PRPT_DATA;
672   procedure (NULL, NULL, NULL, NULL);
673
674   ext = inline_file->ext;
675
676   if (ext && ext->line)
677     {
678       msg (MW, _("Skipping remaining inline data."));
679       for (read_record (inline_file); ext->line; read_record (inline_file))
680         ;
681     }
682   assert (inline_file->ext == NULL);
683
684   return CMD_SUCCESS;
685 }
686
687 static struct fh_ext_class dfm_r_class =
688 {
689   1,
690   N_("reading as a data file"),
691   dfm_close,
692 };
693
694 static struct fh_ext_class dfm_w_class =
695 {
696   2,
697   N_("writing as a data file"),
698   dfm_close,
699 };