Updated the docs and the parser for EXAMINE to make it clearer which options
[pspp] / src / language / data-io / data-reader.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-2004, 2006 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
22 #include <language/data-io/data-reader.h>
23
24 #include <ctype.h>
25 #include <errno.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28
29 #include <data/file-handle-def.h>
30 #include <data/file-name.h>
31 #include <data/procedure.h>
32 #include <language/command.h>
33 #include <language/data-io/file-handle.h>
34 #include <language/lexer/lexer.h>
35 #include <language/line-buffer.h>
36 #include <libpspp/alloc.h>
37 #include <libpspp/assertion.h>
38 #include <libpspp/message.h>
39 #include <libpspp/str.h>
40
41 #include "minmax.h"
42 #include "size_max.h"
43
44 #include "gettext.h"
45 #define _(msgid) gettext (msgid)
46
47 /* Flags for DFM readers. */
48 enum dfm_reader_flags
49   {
50     DFM_ADVANCE = 002,          /* Read next line on dfm_get_record() call? */
51     DFM_SAW_BEGIN_DATA = 004,   /* For inline_file only, whether we've 
52                                    already read a BEGIN DATA line. */
53     DFM_TABS_EXPANDED = 010,    /* Tabs have been expanded. */
54   };
55
56 /* Data file reader. */
57 struct dfm_reader
58   {
59     struct file_handle *fh;     /* File handle. */
60     struct msg_locator where;   /* Current location in data file. */
61     struct string line;         /* Current line. */
62     struct string scratch;      /* Extra line buffer. */
63     enum dfm_reader_flags flags; /* Zero or more of DFM_*. */
64     FILE *file;                 /* Associated file. */
65     size_t pos;                 /* Offset in line of current character. */
66     unsigned eof_cnt;           /* # of attempts to advance past EOF. */
67   };
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   bool is_inline;
75   char *file_name;
76
77   if (r == NULL)
78     return;
79
80   is_inline = r->fh == fh_inline_file ();
81   file_name = is_inline ? NULL : xstrdup (fh_get_file_name (r->fh));
82   still_open = fh_close (r->fh, "data file", "rs");
83   if (still_open) 
84     {
85       free (file_name);
86       return; 
87     }
88
89   if (!is_inline)
90     fn_close (file_name, r->file);
91   else
92     {
93       /* Skip any remaining data on the inline file. */
94       if (r->flags & DFM_SAW_BEGIN_DATA) 
95         {
96           dfm_reread_record (r, 0);
97           while (!dfm_eof (r))
98             dfm_forward_record (r); 
99         }
100     }
101
102   ds_destroy (&r->line);
103   ds_destroy (&r->scratch);
104   free (r);
105   free (file_name);
106 }
107
108 /* Opens the file designated by file handle FH for reading as a
109    data file.  Providing fh_inline_file() for FH designates the
110    "inline file", that is, data included inline in the command
111    file between BEGIN FILE and END FILE.  Returns a reader if
112    successful, or a null pointer otherwise. */
113 struct dfm_reader *
114 dfm_open_reader (struct file_handle *fh)
115 {
116   struct dfm_reader *r;
117   void **rp;
118
119   rp = fh_open (fh, FH_REF_FILE | FH_REF_INLINE, "data file", "rs");
120   if (rp == NULL)
121     return NULL;
122   if (*rp != NULL)
123     return *rp; 
124   
125   r = xmalloc (sizeof *r);
126   r->fh = fh;
127   ds_init_empty (&r->line);
128   ds_init_empty (&r->scratch);
129   r->flags = DFM_ADVANCE;
130   r->eof_cnt = 0;
131   if (fh != fh_inline_file ()) 
132     {
133       r->where.file_name = fh_get_file_name (fh);
134       r->where.line_number = 0; 
135       r->file = fn_open (fh_get_file_name (fh), "rb");
136       if (r->file == NULL)
137         {
138           msg (ME, _("Could not open \"%s\" for reading as a data file: %s."),
139                fh_get_file_name (r->fh), strerror (errno));
140           fh_close (fh,"data file", "rs");
141           free (r);
142           return NULL;
143         }
144     }
145   *rp = r;
146
147   return r;
148 }
149
150 /* Returns true if an I/O error occurred on READER, false otherwise. */
151 bool
152 dfm_reader_error (const struct dfm_reader *r) 
153 {
154   return fh_get_referent (r->fh) == FH_REF_FILE && ferror (r->file);
155 }
156
157 /* Reads a record from the inline file into R.
158    Returns true if successful, false on failure. */
159 static bool
160 read_inline_record (struct dfm_reader *r)
161 {
162   if ((r->flags & DFM_SAW_BEGIN_DATA) == 0)
163     {
164       r->flags |= DFM_SAW_BEGIN_DATA;
165
166       while (token == '.')
167         lex_get ();
168       if (!lex_force_match_id ("BEGIN") || !lex_force_match_id ("DATA"))
169         return false;
170       getl_set_prompt_style (GETL_PROMPT_DATA);
171     }
172       
173   if (!lex_get_line_raw ())
174     {
175       msg (SE, _("Unexpected end-of-file while reading data in BEGIN "
176                  "DATA.  This probably indicates "
177                  "a missing or misformatted END DATA command.  "
178                  "END DATA must appear by itself on a single line "
179                  "with exactly one space between words."));
180       return false;
181     }
182
183   if (ds_length (lex_entire_line_ds() ) >= 8
184       && !strncasecmp (lex_entire_line (), "end data", 8))
185     {
186       lex_discard_line ();
187       return false;
188     }
189
190   ds_assign_string (&r->line, lex_entire_line_ds () );
191
192   return true;
193 }
194
195 /* Reads a record from a disk file into R.
196    Returns true if successful, false on failure. */
197 static bool
198 read_file_record (struct dfm_reader *r)
199 {
200   assert (r->fh != fh_inline_file ());
201   ds_clear (&r->line);
202   if (fh_get_mode (r->fh) == FH_MODE_TEXT)
203     {
204       if (!ds_read_line (&r->line, r->file)) 
205         {
206           if (ferror (r->file))
207             msg (ME, _("Error reading file %s: %s."),
208                  fh_get_name (r->fh), strerror (errno));
209           return false;
210         }
211     }
212   else if (fh_get_mode (r->fh) == FH_MODE_BINARY)
213     {
214       size_t record_width = fh_get_record_width (r->fh);
215       size_t amt = ds_read_stream (&r->line, 1, record_width, r->file);
216       if (record_width != amt)
217         {
218           if (ferror (r->file))
219             msg (ME, _("Error reading file %s: %s."),
220                  fh_get_name (r->fh), strerror (errno));
221           else if (amt != 0)
222             msg (ME, _("%s: Partial record at end of file."),
223                  fh_get_name (r->fh));
224
225           return false;
226         }
227     }
228   else
229     NOT_REACHED ();
230
231   r->where.line_number++;
232
233   return true;
234 }
235
236 /* Reads a record from R, setting the current position to the
237    start of the line.  If an error occurs or end-of-file is
238    encountered, the current line is set to null. */
239 static bool
240 read_record (struct dfm_reader *r)
241 {
242   return (fh_get_referent (r->fh) == FH_REF_FILE
243           ? read_file_record (r)
244           : read_inline_record (r));
245 }
246
247 /* Returns the number of attempts, thus far, to advance past
248    end-of-file in reader R.  Reads forward in HANDLE's file, if
249    necessary, to find out.
250
251    Normally, the user stops attempting to read from the file the
252    first time EOF is reached (a return value of 1).  If the user
253    tries to read past EOF again (a return value of 2 or more),
254    an error message is issued, and the caller should more
255    forcibly abort to avoid an infinite loop. */
256 unsigned
257 dfm_eof (struct dfm_reader *r) 
258 {
259   if (r->flags & DFM_ADVANCE)
260     {
261       r->flags &= ~DFM_ADVANCE;
262
263       if (r->eof_cnt == 0 && read_record (r)) 
264         {
265           r->pos = 0;
266           return 0; 
267         }
268
269       r->eof_cnt++;
270       if (r->eof_cnt == 2)
271         {
272           if (r->fh != fh_inline_file ())
273             msg (ME, _("Attempt to read beyond end-of-file on file %s."),
274                  fh_get_name (r->fh));
275           else
276             msg (ME, _("Attempt to read beyond END DATA."));
277         }
278     }
279
280   return r->eof_cnt;
281 }
282
283 /* Returns the current record in the file corresponding to
284    HANDLE.  Aborts if reading from the file is necessary or at
285    end of file, so call dfm_eof() first. */
286 struct substring
287 dfm_get_record (struct dfm_reader *r)
288 {
289   assert ((r->flags & DFM_ADVANCE) == 0);
290   assert (r->eof_cnt == 0);
291
292   return ds_substr (&r->line, r->pos, SIZE_MAX);
293 }
294
295 /* Expands tabs in the current line into the equivalent number of
296    spaces, if appropriate for this kind of file.  Aborts if
297    reading from the file is necessary or at end of file, so call
298    dfm_eof() first.*/
299 void
300 dfm_expand_tabs (struct dfm_reader *r) 
301 {
302   size_t ofs, new_pos, tab_width;
303
304   assert ((r->flags & DFM_ADVANCE) == 0);
305   assert (r->eof_cnt == 0);
306
307   if (r->flags & DFM_TABS_EXPANDED)
308     return;
309   r->flags |= DFM_TABS_EXPANDED;
310
311   if (r->fh != fh_inline_file ()
312       && (fh_get_mode (r->fh) == FH_MODE_BINARY
313           || fh_get_tab_width (r->fh) == 0
314           || ds_find_char (&r->line, '\t') == SIZE_MAX))
315     return;
316
317   /* Expand tabs from r->line into r->scratch, and figure out
318      new value for r->pos. */
319   tab_width = fh_get_tab_width (r->fh);
320   ds_clear (&r->scratch);
321   new_pos = SIZE_MAX;
322   for (ofs = 0; ofs < ds_length (&r->line); ofs++)
323     {
324       unsigned char c;
325       
326       if (ofs == r->pos)
327         new_pos = ds_length (&r->scratch);
328
329       c = ds_data (&r->line)[ofs];
330       if (c != '\t')
331         ds_put_char (&r->scratch, c);
332       else 
333         {
334           do
335             ds_put_char (&r->scratch, ' ');
336           while (ds_length (&r->scratch) % tab_width != 0);
337         }
338     }
339   if (new_pos == SIZE_MAX) 
340     {
341       /* Maintain the same relationship between position and line
342          length that we had before.  DATA LIST uses a
343          beyond-the-end position to deal with an empty field at
344          the end of the line. */
345       assert (r->pos >= ds_length (&r->line));
346       new_pos = (r->pos - ds_length (&r->line)) + ds_length (&r->scratch);
347     }
348
349   /* Swap r->line and r->scratch and set new r->pos. */
350   ds_swap (&r->line, &r->scratch);
351   r->pos = new_pos;
352 }
353
354 /* Causes dfm_get_record() or dfm_get_whole_record() to read in
355    the next record the next time it is executed on file
356    HANDLE. */
357 void
358 dfm_forward_record (struct dfm_reader *r)
359 {
360   r->flags |= DFM_ADVANCE;
361 }
362
363 /* Cancels the effect of any previous dfm_fwd_record() executed
364    on file HANDLE.  Sets the current line to begin in the 1-based
365    column COLUMN.  */
366 void
367 dfm_reread_record (struct dfm_reader *r, size_t column)
368 {
369   r->flags &= ~DFM_ADVANCE;
370   r->pos = MAX (column, 1) - 1;
371 }
372
373 /* Sets the current line to begin COLUMNS characters following
374    the current start. */
375 void
376 dfm_forward_columns (struct dfm_reader *r, size_t columns)
377 {
378   dfm_reread_record (r, (r->pos + 1) + columns);
379 }
380
381 /* Returns the 1-based column to which the line pointer in HANDLE
382    is set.  Unless dfm_reread_record() or dfm_forward_columns()
383    have been called, this is 1. */
384 size_t
385 dfm_column_start (const struct dfm_reader *r)
386 {
387   return r->pos + 1;
388 }
389
390 /* Returns the number of columns we are currently beyond the end
391    of the line.  At or before end-of-line, this is 0; one column
392    after end-of-line, this is 1; and so on. */
393 size_t
394 dfm_columns_past_end (const struct dfm_reader *r) 
395 {
396   return r->pos < ds_length (&r->line) ? 0 : ds_length (&r->line) - r->pos;
397 }
398
399 /* Returns the 1-based column within the current line that P
400    designates. */
401 size_t
402 dfm_get_column (const struct dfm_reader *r, const char *p) 
403 {
404   return ds_pointer_to_position (&r->line, p) + 1;
405 }
406
407 /* Pushes the file name and line number on the fn/ln stack. */
408 void
409 dfm_push (struct dfm_reader *r)
410 {
411   if (r->fh != fh_inline_file ())
412     msg_push_msg_locator (&r->where);
413 }
414
415 /* Pops the file name and line number from the fn/ln stack. */
416 void
417 dfm_pop (struct dfm_reader *r)
418 {
419   if (r->fh != fh_inline_file ())
420     msg_pop_msg_locator (&r->where);
421 }
422 \f
423 /* BEGIN DATA...END DATA procedure. */
424
425 /* Perform BEGIN DATA...END DATA as a procedure in itself. */
426 int
427 cmd_begin_data (struct dataset *ds)
428 {
429   struct dfm_reader *r;
430   bool ok;
431
432   if (!fh_is_open (fh_inline_file ()))
433     {
434       msg (SE, _("This command is not valid here since the current "
435                  "input program does not access the inline file."));
436       return CMD_CASCADING_FAILURE;
437     }
438
439   /* Open inline file. */
440   r = dfm_open_reader (fh_inline_file ());
441   r->flags |= DFM_SAW_BEGIN_DATA;
442
443   /* Input procedure reads from inline file. */
444   getl_set_prompt_style (GETL_PROMPT_DATA);
445   ok = procedure (ds, NULL, NULL);
446
447   dfm_close_reader (r);
448
449   return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
450 }