786c33e4b1f05727357b6b80c561a1c69cbc2f48
[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 "error.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 /* Flags for DFM readers. */
40 enum dfm_reader_flags
41   {
42     DFM_EOF = 001,              /* At end-of-file? */
43     DFM_ADVANCE = 002,          /* Read next line on dfm_get_record() call? */
44     DFM_SAW_BEGIN_DATA = 004,   /* For inline_file only, whether we've 
45                                    already read a BEGIN DATA line. */
46     DFM_TABS_EXPANDED = 010,    /* Tabs have been expanded. */
47   };
48
49 /* file_handle extension structure. */
50 struct dfm_reader_ext
51   {
52     struct file_ext file;       /* Associated file. */
53
54     struct file_locator where;  /* Current location in data file. */
55     struct string line;         /* Current line. */
56     size_t pos;                 /* Offset in line of current character. */
57     struct string scratch;      /* Extra line buffer. */
58     enum dfm_reader_flags flags; /* Zero or more of DFM_*. */
59   };
60
61 static struct fh_ext_class dfm_r_class;
62
63 static void read_record (struct file_handle *h);
64
65 /* Asserts that H represents a DFM reader and returns H->ext
66    converted to a struct dfm_reader_ext *. */
67 static inline struct dfm_reader_ext *
68 get_reader (struct file_handle *h) 
69 {
70   assert (h != NULL);
71   assert (h->class == &dfm_r_class);
72   assert (h->ext != NULL);
73
74   return h->ext;
75 }
76
77 /* Closes file handle H opened by dfm_open_for_reading(). */
78 static void
79 close_reader (struct file_handle *h)
80 {
81   struct dfm_reader_ext *ext = get_reader (h);
82
83   /* Skip any remaining data on the inline file. */
84   if (h == inline_file)
85     while ((ext->flags & DFM_EOF) == 0)
86       read_record (h);
87       
88   msg (VM (2), _("%s: Closing data-file handle %s."),
89        handle_get_filename (h), handle_get_name (h));
90   assert (h->class == &dfm_r_class);
91   if (ext->file.file)
92     {
93       fn_close_ext (&ext->file);
94       free (ext->file.filename);
95       ext->file.filename = NULL;
96     }
97   ds_destroy (&ext->line);
98   ds_destroy (&ext->scratch);
99   free (ext);
100 }
101
102 /* Opens a file handle for reading as a data file.  Returns
103    nonzero only if successful. */
104 int
105 dfm_open_for_reading (struct file_handle *h)
106 {
107   struct dfm_reader_ext *ext;
108
109   if (h->class != NULL)
110     {
111       if (h->class == &dfm_r_class)
112         return 1;
113       else
114         {
115           msg (ME, _("Cannot read from file %s already opened for %s."),
116                handle_get_name (h), gettext (h->class->name));
117           return 0;
118         }
119     }
120
121   ext = xmalloc (sizeof *ext);
122   ext->where.filename = handle_get_filename (h);
123   ext->where.line_number = 0;
124   ext->file.file = NULL;
125   ds_init (&ext->line, 64);
126   ds_init (&ext->scratch, 0);
127   ext->flags = DFM_ADVANCE;
128
129   msg (VM (1), _("%s: Opening data-file handle %s for reading."),
130        handle_get_filename (h), handle_get_name (h));
131   
132   assert (h != NULL);
133   if (h != inline_file)
134     {
135       ext->file.filename = xstrdup (handle_get_filename (h));
136       ext->file.mode = "rb";
137       ext->file.file = NULL;
138       ext->file.sequence_no = NULL;
139       ext->file.param = NULL;
140       ext->file.postopen = NULL;
141       ext->file.preclose = NULL;
142       if (!fn_open_ext (&ext->file))
143         {
144           msg (ME, _("Could not open \"%s\" for reading "
145                      "as a data file: %s."),
146                handle_get_filename (h), strerror (errno));
147           goto error;
148         }
149     }
150
151   h->class = &dfm_r_class;
152   h->ext = ext;
153   return 1;
154
155  error:
156   err_cond_fail ();
157   free (ext);
158   return 0;
159 }
160
161 /* Reads a record from H->EXT->FILE into H->EXT->LINE, setting
162    H->EXT->PTR to H->EXT->LINE, and setting H->EXT-LEN to the length
163    of the line.  The line is not null-terminated.  If an error occurs
164    or end-of-file is encountered, H->EXT->LINE is set to NULL. */
165 static void
166 read_record (struct file_handle *h)
167 {
168   struct dfm_reader_ext *ext = get_reader (h);
169
170   if (h == inline_file)
171     {
172       if ((ext->flags & DFM_SAW_BEGIN_DATA) == 0)
173         {
174           char *s;
175
176           ext->flags |= DFM_SAW_BEGIN_DATA;
177
178           /* FIXME: WTF can't this just be done with tokens?
179              Is this really a special case? */
180           do
181             {
182               char *cp;
183
184               if (!getl_read_line ())
185                 {
186                   msg (SE, _("BEGIN DATA expected."));
187                   err_failure ();
188                 }
189
190               /* Skip leading whitespace, separate out first
191                  word, so that S points to a single word reduced
192                  to lowercase. */
193               s = ds_c_str (&getl_buf);
194               while (isspace ((unsigned char) *s))
195                 s++;
196               for (cp = s; isalpha ((unsigned char) *cp); cp++)
197                 *cp = tolower ((unsigned char) (*cp));
198               ds_truncate (&getl_buf, cp - s);
199             }
200           while (*s == '\0');
201
202           if (!lex_id_match_len ("begin", 5, s, strcspn (s, " \t\r\v\n")))
203             {
204               msg (SE, _("BEGIN DATA expected."));
205               lex_preprocess_line ();
206               goto eof;
207             }
208           getl_prompt = GETL_PRPT_DATA;
209         }
210       
211       if (!getl_read_line ())
212         {
213           msg (SE, _("Unexpected end-of-file while reading data in BEGIN "
214                      "DATA.  This probably indicates "
215                      "a missing or misformatted END DATA command.  "
216                      "END DATA must appear by itself on a single line "
217                      "with exactly one space between words."));
218           err_failure ();
219         }
220
221       ext->where.line_number++;
222
223       if (ds_length (&getl_buf) >= 8
224           && !strncasecmp (ds_c_str (&getl_buf), "end data", 8))
225         {
226           lex_set_prog (ds_c_str (&getl_buf) + ds_length (&getl_buf));
227           goto eof;
228         }
229
230       ds_replace (&ext->line, ds_c_str (&getl_buf));
231     }
232   else
233     {
234       if (handle_get_mode (h) == MODE_TEXT)
235         {
236           ds_clear (&ext->line);
237           if (!ds_gets (&ext->line, ext->file.file)) 
238             {
239               if (ferror (ext->file.file))
240                 {
241                   msg (ME, _("Error reading file %s: %s."),
242                        handle_get_name (h), strerror (errno));
243                   err_cond_fail ();
244                 }
245               goto eof;
246             }
247         }
248       else if (handle_get_mode (h) == MODE_BINARY)
249         {
250           size_t record_width = handle_get_record_width (h);
251           size_t amt;
252
253           if (ds_length (&ext->line) < record_width) 
254             ds_rpad (&ext->line, record_width, 0);
255           
256           amt = fread (ds_c_str (&ext->line), 1, record_width,
257                        ext->file.file);
258           if (record_width != amt)
259             {
260               if (ferror (ext->file.file))
261                 msg (ME, _("Error reading file %s: %s."),
262                      handle_get_name (h), strerror (errno));
263               else if (amt != 0)
264                 msg (ME, _("%s: Partial record at end of file."),
265                      handle_get_name (h));
266               else
267                 goto eof;
268
269               err_cond_fail ();
270               goto eof;
271             }
272         }
273       else
274         assert (0);
275
276       ext->where.line_number++;
277     }
278
279   ext->pos = 0;
280   return;
281
282 eof:
283   /* Hit eof or an error, clean up everything. */
284   ext->flags |= DFM_EOF;
285 }
286
287 /* Returns nonzero if end of file has been reached on HANDLE.
288    Reads forward in HANDLE's file, if necessary to tell. */
289 int
290 dfm_eof (struct file_handle *h) 
291 {
292   struct dfm_reader_ext *ext = get_reader (h);
293   if (ext->flags & DFM_ADVANCE)
294     {
295       ext->flags &= ~DFM_ADVANCE;
296       if ((ext->flags & DFM_EOF) == 0)
297         read_record (h);
298       else
299         {
300           msg (SE, _("Attempt to read beyond end-of-file on file %s."),
301                handle_get_name (h));
302           err_cond_fail ();
303         }
304     }
305
306   return (ext->flags & DFM_EOF) != 0;
307 }
308
309 /* Returns the current record in the file corresponding to
310    HANDLE.  Aborts if reading from the file is necessary or at
311    end of file, so call dfm_eof() first.  Sets *LINE to the line,
312    which is not null-terminated.  The caller must not free or
313    modify the returned string.  */
314 void
315 dfm_get_record (struct file_handle *h, struct len_string *line)
316 {
317   struct dfm_reader_ext *ext = get_reader (h);
318   assert ((ext->flags & DFM_ADVANCE) == 0);
319   assert ((ext->flags & DFM_EOF) == 0);
320   assert (ext->pos <= ds_length (&ext->line));
321
322   line->string = ds_data (&ext->line) + ext->pos;
323   line->length = ds_length (&ext->line) - ext->pos;
324 }
325
326 /* Expands tabs in the current line into the equivalent number of
327    spaces, if appropriate for this kind of file.  Aborts if
328    reading from the file is necessary or at end of file, so call
329    dfm_eof() first.*/
330 void
331 dfm_expand_tabs (struct file_handle *h) 
332 {
333   struct dfm_reader_ext *ext = get_reader (h);
334   struct string temp;
335   size_t ofs, new_pos, tab_width;
336
337   assert ((ext->flags & DFM_ADVANCE) == 0);
338   assert ((ext->flags & DFM_EOF) == 0);
339   assert (ext->pos <= ds_length (&ext->line));
340
341   if (ext->flags & DFM_TABS_EXPANDED)
342     return;
343   ext->flags |= DFM_TABS_EXPANDED;
344
345   if (handle_get_mode (h) == MODE_BINARY
346       || handle_get_tab_width (h) == 0
347       || memchr (ds_c_str (&ext->line), '\t', ds_length (&ext->line)) == NULL)
348     return;
349
350   /* Expand tabs from ext->line into ext->scratch, and figure out
351      new value for ext->pos. */
352   tab_width = handle_get_tab_width (h);
353   ds_clear (&ext->scratch);
354   new_pos = 0;
355   for (ofs = 0; ofs < ds_length (&ext->line); ofs++)
356     {
357       unsigned char c;
358       
359       if (ofs == ext->pos)
360         new_pos = ds_length (&ext->scratch);
361
362       c = ds_c_str (&ext->line)[ofs];
363       if (c != '\t')
364         ds_putc (&ext->scratch, c);
365       else 
366         {
367           do
368             ds_putc (&ext->scratch, ' ');
369           while (ds_length (&ext->scratch) % tab_width != 0);
370         }
371     }
372
373   /* Swap ext->line and ext->scratch and set new ext->pos. */
374   temp = ext->line;
375   ext->line = ext->scratch;
376   ext->scratch = temp;
377   ext->pos = new_pos;
378 }
379
380 /* Causes dfm_get_record() to read in the next record the next time it
381    is executed on file HANDLE. */
382 void
383 dfm_forward_record (struct file_handle *h)
384 {
385   struct dfm_reader_ext *ext = get_reader (h);
386   ext->flags |= DFM_ADVANCE;
387 }
388
389 /* Cancels the effect of any previous dfm_fwd_record() executed
390    on file HANDLE.  Sets the current line to begin in the 1-based
391    column COLUMN.  */
392 void
393 dfm_reread_record (struct file_handle *h, size_t column)
394 {
395   struct dfm_reader_ext *ext = get_reader (h);
396   ext->flags &= ~DFM_ADVANCE;
397   if (column < 1)
398     ext->pos = 0;
399   else if (column > ds_length (&ext->line))
400     ext->pos = ds_length (&ext->line);
401   else
402     ext->pos = column - 1;
403 }
404
405 /* Sets the current line to begin COLUMNS characters following
406    the current start. */
407 void
408 dfm_forward_columns (struct file_handle *h, size_t columns)
409 {
410   struct dfm_reader_ext *ext = get_reader (h);
411   dfm_reread_record (h, (ext->pos + 1) + columns);
412 }
413
414 /* Returns the 1-based column to which the line pointer in HANDLE
415    is set.  Unless dfm_reread_record() or dfm_forward_columns()
416    have been called, this is 1. */
417 size_t
418 dfm_column_start (struct file_handle *h)
419 {
420   struct dfm_reader_ext *ext = get_reader (h);
421   return ext->pos + 1;
422 }
423
424 /* Pushes the filename and line number on the fn/ln stack. */
425 void
426 dfm_push (struct file_handle *h)
427 {
428   struct dfm_reader_ext *ext = get_reader (h);
429   if (h != inline_file)
430     err_push_file_locator (&ext->where);
431 }
432
433 /* Pops the filename and line number from the fn/ln stack. */
434 void
435 dfm_pop (struct file_handle *h)
436 {
437   struct dfm_reader_ext *ext = get_reader (h);
438   if (h != inline_file)
439     err_pop_file_locator (&ext->where);
440 }
441
442 /* DFM reader class. */
443 static struct fh_ext_class dfm_r_class =
444 {
445   1,
446   N_("reading as a data file"),
447   close_reader,
448 };
449 \f
450 /* file_handle extension structure. */
451 struct dfm_writer_ext
452   {
453     struct file_ext file;       /* Associated file. */
454     struct file_locator where;  /* Current location in data file. */
455     char *bounce;               /* Bounce buffer for fixed-size fields. */
456   };
457
458 static struct fh_ext_class dfm_w_class;
459
460 /* Opens a file handle for writing as a data file. */
461 int
462 dfm_open_for_writing (struct file_handle *h)
463 {
464   struct dfm_writer_ext *ext;
465   
466   if (h->class != NULL)
467     {
468       if (h->class == &dfm_w_class)
469         return 1;
470       else
471         {
472           msg (ME, _("Cannot write to file %s already opened for %s."),
473                handle_get_name (h), gettext (h->class->name));
474           err_cond_fail ();
475           return 0;
476         }
477     }
478
479   ext = xmalloc (sizeof *ext);
480   ext->where.filename = handle_get_filename (h);
481   ext->where.line_number = 0;
482   ext->file.file = NULL;
483   ext->bounce = NULL;
484
485   msg (VM (1), _("%s: Opening data-file handle %s for writing."),
486        handle_get_filename (h), handle_get_name (h));
487   
488   assert (h != NULL);
489   if (h == inline_file)
490     {
491       msg (ME, _("Cannot open the inline file for writing."));
492       goto error;
493     }
494
495   ext->file.filename = xstrdup (handle_get_filename (h));
496   ext->file.mode = "wb";
497   ext->file.file = NULL;
498   ext->file.sequence_no = NULL;
499   ext->file.param = NULL;
500   ext->file.postopen = NULL;
501   ext->file.preclose = NULL;
502       
503   if (!fn_open_ext (&ext->file))
504     {
505       msg (ME, _("An error occurred while opening \"%s\" for writing "
506                  "as a data file: %s."),
507            handle_get_filename (h), strerror (errno));
508       goto error;
509     }
510
511   h->class = &dfm_w_class;
512   h->ext = ext;
513   return 1;
514
515  error:
516   free (ext);
517   err_cond_fail ();
518   return 0;
519 }
520
521 /* Writes record REC having length LEN to the file corresponding to
522    HANDLE.  REC is not null-terminated.  Returns nonzero on success,
523    zero on failure. */
524 int
525 dfm_put_record (struct file_handle *h, const char *rec, size_t len)
526 {
527   struct dfm_writer_ext *ext;
528
529   assert (h != NULL);
530   assert (h->class == &dfm_w_class);
531   assert (h->ext != NULL);
532
533   ext = h->ext;
534   if (handle_get_mode (h) == MODE_BINARY && len < handle_get_record_width (h))
535     {
536       size_t rec_width = handle_get_record_width (h);
537       if (ext->bounce == NULL)
538         ext->bounce = xmalloc (rec_width);
539       memcpy (ext->bounce, rec, len);
540       memset (&ext->bounce[len], 0, rec_width - len);
541       rec = ext->bounce;
542       len = rec_width;
543     }
544
545   if (fwrite (rec, len, 1, ext->file.file) != 1)
546     {
547       msg (ME, _("Error writing file %s: %s."),
548            handle_get_name (h), strerror (errno));
549       err_cond_fail ();
550       return 0;
551     }
552
553   return 1;
554 }
555
556 /* Closes file handle H opened by dfm_open_for_writing(). */
557 static void
558 close_writer (struct file_handle *h)
559 {
560   struct dfm_writer_ext *ext;
561
562   assert (h->class == &dfm_w_class);
563   ext = h->ext;
564
565   msg (VM (2), _("%s: Closing data-file handle %s."),
566        handle_get_filename (h), handle_get_name (h));
567   if (ext->file.file)
568     {
569       fn_close_ext (&ext->file);
570       free (ext->file.filename);
571       ext->file.filename = NULL;
572     }
573   free (ext->bounce);
574   free (ext);
575 }
576
577 /* DFM writer class. */
578 static struct fh_ext_class dfm_w_class =
579 {
580   2,
581   N_("writing as a data file"),
582   close_writer,
583 };
584 \f
585 /* BEGIN DATA...END DATA procedure. */
586
587 /* Perform BEGIN DATA...END DATA as a procedure in itself. */
588 int
589 cmd_begin_data (void)
590 {
591   struct dfm_reader_ext *ext;
592
593   /* FIXME: figure out the *exact* conditions, not these really
594      lenient conditions. */
595   if (vfm_source == NULL
596       || case_source_is_class (vfm_source, &storage_source_class)
597       || case_source_is_class (vfm_source, &sort_source_class))
598     {
599       msg (SE, _("This command is not valid here since the current "
600                  "input program does not access the inline file."));
601       err_cond_fail ();
602       return CMD_FAILURE;
603     }
604
605   /* Initialize inline_file. */
606   msg (VM (1), _("inline file: Opening for reading."));
607   dfm_open_for_reading (inline_file);
608   ext = inline_file->ext;
609   ext->flags |= DFM_SAW_BEGIN_DATA;
610
611   /* We don't actually read from the inline file.  The input procedure
612      is what reads from it. */
613   getl_prompt = GETL_PRPT_DATA;
614   procedure (NULL, NULL);
615   
616   ext = inline_file->ext;
617   if (ext && (ext->flags & DFM_EOF) == 0)
618     {
619       msg (MW, _("Skipping remaining inline data."));
620       while ((ext->flags & DFM_EOF) == 0)
621         read_record (inline_file);
622     }
623   assert (inline_file->ext == NULL);
624
625   return CMD_SUCCESS;
626 }