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