Apply patch #5225, assertions.
[pspp-builds.git] / 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 (!getl_read_line (NULL))
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 (&getl_buf) >= 8
184       && !strncasecmp (ds_cstr (&getl_buf), "end data", 8))
185     {
186       lex_set_prog (ds_end (&getl_buf));
187       return false;
188     }
189
190   ds_assign_string (&r->line, &getl_buf);
191   return true;
192 }
193
194 /* Reads a record from a disk file into R.
195    Returns true if successful, false on failure. */
196 static bool
197 read_file_record (struct dfm_reader *r)
198 {
199   assert (r->fh != fh_inline_file ());
200   ds_clear (&r->line);
201   if (fh_get_mode (r->fh) == FH_MODE_TEXT)
202     {
203       if (!ds_read_line (&r->line, r->file)) 
204         {
205           if (ferror (r->file))
206             msg (ME, _("Error reading file %s: %s."),
207                  fh_get_name (r->fh), strerror (errno));
208           return false;
209         }
210     }
211   else if (fh_get_mode (r->fh) == FH_MODE_BINARY)
212     {
213       size_t record_width = fh_get_record_width (r->fh);
214       size_t amt = ds_read_stream (&r->line, 1, record_width, r->file);
215       if (record_width != amt)
216         {
217           if (ferror (r->file))
218             msg (ME, _("Error reading file %s: %s."),
219                  fh_get_name (r->fh), strerror (errno));
220           else if (amt != 0)
221             msg (ME, _("%s: Partial record at end of file."),
222                  fh_get_name (r->fh));
223
224           return false;
225         }
226     }
227   else
228     NOT_REACHED ();
229
230   r->where.line_number++;
231
232   return true;
233 }
234
235 /* Reads a record from R, setting the current position to the
236    start of the line.  If an error occurs or end-of-file is
237    encountered, the current line is set to null. */
238 static bool
239 read_record (struct dfm_reader *r)
240 {
241   return (fh_get_referent (r->fh) == FH_REF_FILE
242           ? read_file_record (r)
243           : read_inline_record (r));
244 }
245
246 /* Returns the number of attempts, thus far, to advance past
247    end-of-file in reader R.  Reads forward in HANDLE's file, if
248    necessary, to find out.
249
250    Normally, the user stops attempting to read from the file the
251    first time EOF is reached (a return value of 1).  If the user
252    tries to read past EOF again (a return value of 2 or more),
253    an error message is issued, and the caller should more
254    forcibly abort to avoid an infinite loop. */
255 unsigned
256 dfm_eof (struct dfm_reader *r) 
257 {
258   if (r->flags & DFM_ADVANCE)
259     {
260       r->flags &= ~DFM_ADVANCE;
261
262       if (r->eof_cnt == 0 && read_record (r)) 
263         {
264           r->pos = 0;
265           return 0; 
266         }
267
268       r->eof_cnt++;
269       if (r->eof_cnt == 2)
270         {
271           if (r->fh != fh_inline_file ())
272             msg (ME, _("Attempt to read beyond end-of-file on file %s."),
273                  fh_get_name (r->fh));
274           else
275             msg (ME, _("Attempt to read beyond END DATA."));
276         }
277     }
278
279   return r->eof_cnt;
280 }
281
282 /* Returns the current record in the file corresponding to
283    HANDLE.  Aborts if reading from the file is necessary or at
284    end of file, so call dfm_eof() first. */
285 struct substring
286 dfm_get_record (struct dfm_reader *r)
287 {
288   assert ((r->flags & DFM_ADVANCE) == 0);
289   assert (r->eof_cnt == 0);
290
291   return ds_substr (&r->line, r->pos, SIZE_MAX);
292 }
293
294 /* Expands tabs in the current line into the equivalent number of
295    spaces, if appropriate for this kind of file.  Aborts if
296    reading from the file is necessary or at end of file, so call
297    dfm_eof() first.*/
298 void
299 dfm_expand_tabs (struct dfm_reader *r) 
300 {
301   size_t ofs, new_pos, tab_width;
302
303   assert ((r->flags & DFM_ADVANCE) == 0);
304   assert (r->eof_cnt == 0);
305
306   if (r->flags & DFM_TABS_EXPANDED)
307     return;
308   r->flags |= DFM_TABS_EXPANDED;
309
310   if (r->fh != fh_inline_file ()
311       && (fh_get_mode (r->fh) == FH_MODE_BINARY
312           || fh_get_tab_width (r->fh) == 0
313           || ds_find_char (&r->line, '\t') == SIZE_MAX))
314     return;
315
316   /* Expand tabs from r->line into r->scratch, and figure out
317      new value for r->pos. */
318   tab_width = fh_get_tab_width (r->fh);
319   ds_clear (&r->scratch);
320   new_pos = SIZE_MAX;
321   for (ofs = 0; ofs < ds_length (&r->line); ofs++)
322     {
323       unsigned char c;
324       
325       if (ofs == r->pos)
326         new_pos = ds_length (&r->scratch);
327
328       c = ds_data (&r->line)[ofs];
329       if (c != '\t')
330         ds_put_char (&r->scratch, c);
331       else 
332         {
333           do
334             ds_put_char (&r->scratch, ' ');
335           while (ds_length (&r->scratch) % tab_width != 0);
336         }
337     }
338   if (new_pos == SIZE_MAX) 
339     {
340       /* Maintain the same relationship between position and line
341          length that we had before.  DATA LIST uses a
342          beyond-the-end position to deal with an empty field at
343          the end of the line. */
344       assert (r->pos >= ds_length (&r->line));
345       new_pos = (r->pos - ds_length (&r->line)) + ds_length (&r->scratch);
346     }
347
348   /* Swap r->line and r->scratch and set new r->pos. */
349   ds_swap (&r->line, &r->scratch);
350   r->pos = new_pos;
351 }
352
353 /* Causes dfm_get_record() or dfm_get_whole_record() to read in
354    the next record the next time it is executed on file
355    HANDLE. */
356 void
357 dfm_forward_record (struct dfm_reader *r)
358 {
359   r->flags |= DFM_ADVANCE;
360 }
361
362 /* Cancels the effect of any previous dfm_fwd_record() executed
363    on file HANDLE.  Sets the current line to begin in the 1-based
364    column COLUMN.  */
365 void
366 dfm_reread_record (struct dfm_reader *r, size_t column)
367 {
368   r->flags &= ~DFM_ADVANCE;
369   r->pos = MAX (column, 1) - 1;
370 }
371
372 /* Sets the current line to begin COLUMNS characters following
373    the current start. */
374 void
375 dfm_forward_columns (struct dfm_reader *r, size_t columns)
376 {
377   dfm_reread_record (r, (r->pos + 1) + columns);
378 }
379
380 /* Returns the 1-based column to which the line pointer in HANDLE
381    is set.  Unless dfm_reread_record() or dfm_forward_columns()
382    have been called, this is 1. */
383 size_t
384 dfm_column_start (const struct dfm_reader *r)
385 {
386   return r->pos + 1;
387 }
388
389 /* Returns the number of columns we are currently beyond the end
390    of the line.  At or before end-of-line, this is 0; one column
391    after end-of-line, this is 1; and so on. */
392 size_t
393 dfm_columns_past_end (const struct dfm_reader *r) 
394 {
395   return r->pos < ds_length (&r->line) ? 0 : ds_length (&r->line) - r->pos;
396 }
397
398 /* Returns the 1-based column within the current line that P
399    designates. */
400 size_t
401 dfm_get_column (const struct dfm_reader *r, const char *p) 
402 {
403   return ds_pointer_to_position (&r->line, p) + 1;
404 }
405
406 /* Pushes the file name and line number on the fn/ln stack. */
407 void
408 dfm_push (struct dfm_reader *r)
409 {
410   if (r->fh != fh_inline_file ())
411     msg_push_msg_locator (&r->where);
412 }
413
414 /* Pops the file name and line number from the fn/ln stack. */
415 void
416 dfm_pop (struct dfm_reader *r)
417 {
418   if (r->fh != fh_inline_file ())
419     msg_pop_msg_locator (&r->where);
420 }
421 \f
422 /* BEGIN DATA...END DATA procedure. */
423
424 /* Perform BEGIN DATA...END DATA as a procedure in itself. */
425 int
426 cmd_begin_data (void)
427 {
428   struct dfm_reader *r;
429   bool ok;
430
431   if (!fh_is_open (fh_inline_file ()))
432     {
433       msg (SE, _("This command is not valid here since the current "
434                  "input program does not access the inline file."));
435       return CMD_CASCADING_FAILURE;
436     }
437
438   /* Open inline file. */
439   r = dfm_open_reader (fh_inline_file ());
440   r->flags |= DFM_SAW_BEGIN_DATA;
441
442   /* Input procedure reads from inline file. */
443   getl_set_prompt_style (GETL_PROMPT_DATA);
444   ok = procedure (NULL, NULL);
445
446   dfm_close_reader (r);
447
448   return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
449 }