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