a739b5c32f1bd66d5cb531c1edf9a71738fd220d
[pspp-builds.git] / src / dfm-read.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-2004 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., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, USA. */
19
20 #include <config.h>
21 #include "dfm-read.h"
22 #include <ctype.h>
23 #include <errno.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include "alloc.h"
27 #include "command.h"
28 #include "error.h"
29 #include "file-handle.h"
30 #include "file-handle-def.h"
31 #include "filename.h"
32 #include "getl.h"
33 #include "lexer.h"
34 #include "str.h"
35 #include "vfm.h"
36
37 #include "gettext.h"
38 #define _(msgid) gettext (msgid)
39
40 #include "debug-print.h"
41
42 /* Flags for DFM readers. */
43 enum dfm_reader_flags
44   {
45     DFM_EOF = 001,              /* At end-of-file? */
46     DFM_ADVANCE = 002,          /* Read next line on dfm_get_record() call? */
47     DFM_SAW_BEGIN_DATA = 004,   /* For inline_file only, whether we've 
48                                    already read a BEGIN DATA line. */
49     DFM_TABS_EXPANDED = 010,    /* Tabs have been expanded. */
50   };
51
52 /* Data file reader. */
53 struct dfm_reader
54   {
55     struct file_handle *fh;     /* File handle. */
56     struct file_ext file;       /* Associated file. */
57     struct file_locator where;  /* Current location in data file. */
58     struct string line;         /* Current line. */
59     size_t pos;                 /* Offset in line of current character. */
60     struct string scratch;      /* Extra line buffer. */
61     enum dfm_reader_flags flags; /* Zero or more of DFM_*. */
62   };
63
64 static int inline_open_cnt;
65 static struct dfm_reader *inline_file;
66
67 static void read_record (struct dfm_reader *r);
68
69 /* Closes reader R opened by dfm_open_reader(). */
70 void
71 dfm_close_reader (struct dfm_reader *r)
72 {
73   int still_open;
74
75   if (r == NULL)
76     return;
77
78   if (r->fh != NULL) 
79     still_open = fh_close (r->fh, "data file", "rs");
80   else
81     {
82       assert (inline_open_cnt > 0);
83       still_open = --inline_open_cnt;
84
85       if (!still_open) 
86         {
87           /* Skip any remaining data on the inline file. */
88           if (r->flags & DFM_SAW_BEGIN_DATA)
89             while ((r->flags & DFM_EOF) == 0)
90               read_record (r);
91           inline_file = NULL;
92         }
93     }
94   if (still_open)
95     return;
96
97   if (r->fh != NULL && r->file.file)
98     {
99       fn_close_ext (&r->file);
100       free (r->file.filename);
101       r->file.filename = NULL;
102     }
103   ds_destroy (&r->line);
104   ds_destroy (&r->scratch);
105   free (r);
106 }
107
108 /* Opens the file designated by file handle FH for reading as a
109    data file.  Providing a null pointer for FH designates the
110    "inline file", that is, data included inline in the command
111    file between BEGIN FILE and END FILE.  Returns nonzero only if
112    successful. */
113 struct dfm_reader *
114 dfm_open_reader (struct file_handle *fh)
115 {
116   struct dfm_reader *r;
117   void **rp;
118
119   if (fh != NULL) 
120     {
121       rp = fh_open (fh, "data file", "rs");
122       if (rp == NULL)
123         return NULL;
124       if (*rp != NULL)
125         return *rp; 
126     }
127   else 
128     {
129       assert (inline_open_cnt >= 0);
130       if (inline_open_cnt++ > 0)
131         return inline_file;
132       rp = NULL;
133     }
134   
135   r = xmalloc (sizeof *r);
136   r->fh = fh;
137   if (fh != NULL) 
138     {
139       r->where.filename = fh_get_filename (fh);
140       r->where.line_number = 0; 
141     }
142   r->file.file = NULL;
143   ds_init (&r->line, 64);
144   ds_init (&r->scratch, 0);
145   r->flags = DFM_ADVANCE;
146
147   if (fh != NULL)
148     {
149       r->file.filename = xstrdup (fh_get_filename (r->fh));
150       r->file.mode = "rb";
151       r->file.file = NULL;
152       r->file.sequence_no = NULL;
153       r->file.param = NULL;
154       r->file.postopen = NULL;
155       r->file.preclose = NULL;
156       if (!fn_open_ext (&r->file))
157         {
158           msg (ME, _("Could not open \"%s\" for reading "
159                      "as a data file: %s."),
160                fh_get_filename (r->fh), strerror (errno));
161           err_cond_fail ();
162           fh_close (fh,"data file", "rs");
163           free (r);
164           return NULL;
165         }
166       *rp = r;
167     }
168   else
169     inline_file = r;
170
171   return r;
172 }
173
174 static int
175 read_inline_record (struct dfm_reader *r)
176 {
177   if ((r->flags & DFM_SAW_BEGIN_DATA) == 0)
178     {
179       char *s;
180
181       r->flags |= DFM_SAW_BEGIN_DATA;
182
183       /* FIXME: WTF can't this just be done with tokens?
184          Is this really a special case? */
185       do
186         {
187           char *cp;
188
189           if (!getl_read_line ())
190             {
191               msg (SE, _("BEGIN DATA expected."));
192               err_failure ();
193             }
194
195           /* Skip leading whitespace, separate out first
196              word, so that S points to a single word reduced
197              to lowercase. */
198           s = ds_c_str (&getl_buf);
199           while (isspace ((unsigned char) *s))
200             s++;
201           for (cp = s; isalpha ((unsigned char) *cp); cp++)
202             *cp = tolower ((unsigned char) (*cp));
203           ds_truncate (&getl_buf, cp - s);
204         }
205       while (*s == '\0');
206
207       if (!lex_id_match_len ("begin", 5, s, strcspn (s, " \t\r\v\n")))
208         {
209           msg (SE, _("BEGIN DATA expected."));
210           lex_preprocess_line ();
211           return 0;
212         }
213       getl_prompt = GETL_PRPT_DATA;
214     }
215       
216   if (!getl_read_line ())
217     {
218       msg (SE, _("Unexpected end-of-file while reading data in BEGIN "
219                  "DATA.  This probably indicates "
220                  "a missing or misformatted END DATA command.  "
221                  "END DATA must appear by itself on a single line "
222                  "with exactly one space between words."));
223       err_failure ();
224     }
225
226   if (r->fh != NULL)
227     r->where.line_number++;
228
229   if (ds_length (&getl_buf) >= 8
230       && !strncasecmp (ds_c_str (&getl_buf), "end data", 8))
231     {
232       lex_set_prog (ds_c_str (&getl_buf) + ds_length (&getl_buf));
233       return 0;
234     }
235
236   ds_replace (&r->line, ds_c_str (&getl_buf));
237   return 1;
238 }
239
240 static int
241 read_file_record (struct dfm_reader *r)
242 {
243   assert (r->fh != NULL);
244   if (fh_get_mode (r->fh) == MODE_TEXT)
245     {
246       ds_clear (&r->line);
247       if (!ds_gets (&r->line, r->file.file)) 
248         {
249           if (ferror (r->file.file))
250             {
251               msg (ME, _("Error reading file %s: %s."),
252                    fh_get_name (r->fh), strerror (errno));
253               err_cond_fail ();
254             }
255           return 0;
256         }
257     }
258   else if (fh_get_mode (r->fh) == MODE_BINARY)
259     {
260       size_t record_width = fh_get_record_width (r->fh);
261       size_t amt;
262
263       if (ds_length (&r->line) < record_width) 
264         ds_rpad (&r->line, record_width, 0);
265           
266       amt = fread (ds_c_str (&r->line), 1, record_width,
267                    r->file.file);
268       if (record_width != amt)
269         {
270           if (ferror (r->file.file))
271             msg (ME, _("Error reading file %s: %s."),
272                  fh_get_name (r->fh), strerror (errno));
273           else if (amt != 0)
274             msg (ME, _("%s: Partial record at end of file."),
275                  fh_get_name (r->fh));
276           else
277             return 0;
278
279           err_cond_fail ();
280           return 0;
281         }
282     }
283   else
284     assert (0);
285
286   r->where.line_number++;
287
288   return 1;
289 }
290
291 /* Reads a record from R, setting the current position to the
292    start of the line.  If an error occurs or end-of-file is
293    encountered, the current line is set to null. */
294 static void
295 read_record (struct dfm_reader *r)
296 {
297   int success = r->fh != NULL ? read_file_record (r) : read_inline_record (r);
298   if (success)
299     r->pos = 0;
300   else
301     r->flags |= DFM_EOF;
302 }
303
304 /* Returns nonzero if end of file has been reached on HANDLE.
305    Reads forward in HANDLE's file, if necessary to tell. */
306 int
307 dfm_eof (struct dfm_reader *r) 
308 {
309   if (r->flags & DFM_ADVANCE)
310     {
311       r->flags &= ~DFM_ADVANCE;
312       if ((r->flags & DFM_EOF) == 0)
313         read_record (r);
314       else
315         {
316           if (r->fh != NULL)
317             msg (SE, _("Attempt to read beyond end-of-file on file %s."),
318                  fh_get_name (r->fh));
319           else
320             msg (SE, _("Attempt to read beyond END DATA."));
321           err_cond_fail ();
322         }
323     }
324
325   return (r->flags & DFM_EOF) != 0;
326 }
327
328 /* Returns the current record in the file corresponding to
329    HANDLE.  Aborts if reading from the file is necessary or at
330    end of file, so call dfm_eof() first.  Sets *LINE to the line,
331    which is not null-terminated.  The caller must not free or
332    modify the returned string.  */
333 void
334 dfm_get_record (struct dfm_reader *r, struct fixed_string *line)
335 {
336   assert ((r->flags & DFM_ADVANCE) == 0);
337   assert ((r->flags & DFM_EOF) == 0);
338   assert (r->pos <= ds_length (&r->line));
339
340   line->string = ds_data (&r->line) + r->pos;
341   line->length = ds_length (&r->line) - r->pos;
342 }
343
344 /* Expands tabs in the current line into the equivalent number of
345    spaces, if appropriate for this kind of file.  Aborts if
346    reading from the file is necessary or at end of file, so call
347    dfm_eof() first.*/
348 void
349 dfm_expand_tabs (struct dfm_reader *r) 
350 {
351   struct string temp;
352   size_t ofs, new_pos, tab_width;
353
354   assert ((r->flags & DFM_ADVANCE) == 0);
355   assert ((r->flags & DFM_EOF) == 0);
356   assert (r->pos <= ds_length (&r->line));
357
358   if (r->flags & DFM_TABS_EXPANDED)
359     return;
360   r->flags |= DFM_TABS_EXPANDED;
361
362   if (r->fh != NULL
363       && (fh_get_mode (r->fh) == MODE_BINARY
364           || fh_get_tab_width (r->fh) == 0
365           || memchr (ds_c_str (&r->line), '\t', ds_length (&r->line)) == NULL))
366     return;
367
368   /* Expand tabs from r->line into r->scratch, and figure out
369      new value for r->pos. */
370   tab_width = r->fh != NULL ? fh_get_tab_width (r->fh) : 8;
371   ds_clear (&r->scratch);
372   new_pos = 0;
373   for (ofs = 0; ofs < ds_length (&r->line); ofs++)
374     {
375       unsigned char c;
376       
377       if (ofs == r->pos)
378         new_pos = ds_length (&r->scratch);
379
380       c = ds_c_str (&r->line)[ofs];
381       if (c != '\t')
382         ds_putc (&r->scratch, c);
383       else 
384         {
385           do
386             ds_putc (&r->scratch, ' ');
387           while (ds_length (&r->scratch) % tab_width != 0);
388         }
389     }
390
391   /* Swap r->line and r->scratch and set new r->pos. */
392   temp = r->line;
393   r->line = r->scratch;
394   r->scratch = temp;
395   r->pos = new_pos;
396 }
397
398 /* Causes dfm_get_record() to read in the next record the next time it
399    is executed on file HANDLE. */
400 void
401 dfm_forward_record (struct dfm_reader *r)
402 {
403   r->flags |= DFM_ADVANCE;
404 }
405
406 /* Cancels the effect of any previous dfm_fwd_record() executed
407    on file HANDLE.  Sets the current line to begin in the 1-based
408    column COLUMN.  */
409 void
410 dfm_reread_record (struct dfm_reader *r, size_t column)
411 {
412   r->flags &= ~DFM_ADVANCE;
413   if (column < 1)
414     r->pos = 0;
415   else if (column > ds_length (&r->line))
416     r->pos = ds_length (&r->line);
417   else
418     r->pos = column - 1;
419 }
420
421 /* Sets the current line to begin COLUMNS characters following
422    the current start. */
423 void
424 dfm_forward_columns (struct dfm_reader *r, size_t columns)
425 {
426   dfm_reread_record (r, (r->pos + 1) + columns);
427 }
428
429 /* Returns the 1-based column to which the line pointer in HANDLE
430    is set.  Unless dfm_reread_record() or dfm_forward_columns()
431    have been called, this is 1. */
432 size_t
433 dfm_column_start (struct dfm_reader *r)
434 {
435   return r->pos + 1;
436 }
437
438 /* Pushes the filename and line number on the fn/ln stack. */
439 void
440 dfm_push (struct dfm_reader *r)
441 {
442   if (r->fh != NULL)
443     err_push_file_locator (&r->where);
444 }
445
446 /* Pops the filename and line number from the fn/ln stack. */
447 void
448 dfm_pop (struct dfm_reader *r)
449 {
450   if (r->fh != NULL)
451     err_pop_file_locator (&r->where);
452 }
453 \f
454 /* BEGIN DATA...END DATA procedure. */
455
456 /* Perform BEGIN DATA...END DATA as a procedure in itself. */
457 int
458 cmd_begin_data (void)
459 {
460   struct dfm_reader *r;
461
462   /* FIXME: figure out the *exact* conditions, not these really
463      lenient conditions. */
464   if (vfm_source == NULL
465       || case_source_is_class (vfm_source, &storage_source_class))
466     {
467       msg (SE, _("This command is not valid here since the current "
468                  "input program does not access the inline file."));
469       err_cond_fail ();
470       return CMD_FAILURE;
471     }
472
473   /* Open inline file. */
474   r = dfm_open_reader (NULL);
475   r->flags |= DFM_SAW_BEGIN_DATA;
476
477   /* Input procedure reads from inline file. */
478   getl_prompt = GETL_PRPT_DATA;
479   procedure (NULL, NULL);
480
481   dfm_close_reader (r);
482
483   return CMD_SUCCESS;
484 }