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