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