xalloc.h-instead-of-alloc.h.patch from patch #6230.
[pspp-builds.git] / src / language / data-io / data-reader.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-2004, 2006 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <language/data-io/data-reader.h>
20
21 #include <ctype.h>
22 #include <errno.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25
26 #include <data/casereader.h>
27 #include <data/file-handle-def.h>
28 #include <data/file-name.h>
29 #include <data/procedure.h>
30 #include <language/command.h>
31 #include <language/data-io/file-handle.h>
32 #include <language/lexer/lexer.h>
33 #include <language/prompt.h>
34 #include <libpspp/assertion.h>
35 #include <libpspp/message.h>
36 #include <libpspp/str.h>
37
38 #include "minmax.h"
39 #include "size_max.h"
40 #include "xalloc.h"
41
42 #include "gettext.h"
43 #define _(msgid) gettext (msgid)
44
45 /* Flags for DFM readers. */
46 enum dfm_reader_flags
47   {
48     DFM_ADVANCE = 002,          /* Read next line on dfm_get_record() call? */
49     DFM_SAW_BEGIN_DATA = 004,   /* For inline_file only, whether we've
50                                    already read a BEGIN DATA line. */
51     DFM_TABS_EXPANDED = 010,    /* Tabs have been expanded. */
52   };
53
54 /* Data file reader. */
55 struct dfm_reader
56   {
57     struct file_handle *fh;     /* File handle. */
58     struct msg_locator where;   /* Current location in data file. */
59     struct string line;         /* Current line. */
60     struct string scratch;      /* Extra line buffer. */
61     enum dfm_reader_flags flags; /* Zero or more of DFM_*. */
62     FILE *file;                 /* Associated file. */
63     size_t pos;                 /* Offset in line of current character. */
64     unsigned eof_cnt;           /* # of attempts to advance past EOF. */
65     struct lexer *lexer;        /* The lexer reading the file */
66   };
67
68 /* Closes reader R opened by dfm_open_reader(). */
69 void
70 dfm_close_reader (struct dfm_reader *r)
71 {
72   int still_open;
73   bool is_inline;
74   char *file_name;
75
76   if (r == NULL)
77     return;
78
79   is_inline = r->fh == fh_inline_file ();
80   file_name = is_inline ? NULL : xstrdup (fh_get_file_name (r->fh));
81   still_open = fh_close (r->fh, "data file", "rs");
82   if (still_open)
83     {
84       free (file_name);
85       return;
86     }
87
88   if (!is_inline)
89     fn_close (file_name, r->file);
90   else
91     {
92       /* Skip any remaining data on the inline file. */
93       if (r->flags & DFM_SAW_BEGIN_DATA)
94         {
95           dfm_reread_record (r, 0);
96           while (!dfm_eof (r))
97             dfm_forward_record (r);
98         }
99     }
100
101   ds_destroy (&r->line);
102   ds_destroy (&r->scratch);
103   free (r);
104   free (file_name);
105 }
106
107 /* Opens the file designated by file handle FH for reading as a
108    data file.  Providing fh_inline_file() for FH designates the
109    "inline file", that is, data included inline in the command
110    file between BEGIN FILE and END FILE.  Returns a reader if
111    successful, or a null pointer otherwise. */
112 struct dfm_reader *
113 dfm_open_reader (struct file_handle *fh, struct lexer *lexer)
114 {
115   struct dfm_reader *r;
116   void **rp;
117
118   rp = fh_open (fh, FH_REF_FILE | FH_REF_INLINE, "data file", "rs");
119   if (rp == NULL)
120     return NULL;
121   if (*rp != NULL)
122     return *rp;
123
124   r = xmalloc (sizeof *r);
125   r->fh = fh;
126   r->lexer = lexer ;
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 (lex_token (r->lexer) == '.')
167         lex_get (r->lexer);
168       if (!lex_force_match_id (r->lexer, "BEGIN") || !lex_force_match_id (r->lexer, "DATA"))
169         return false;
170       prompt_set_style (PROMPT_DATA);
171     }
172
173   if (!lex_get_line_raw (r->lexer))
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 (r->lexer) ) >= 8
184       && !strncasecmp (lex_entire_line (r->lexer), "end data", 8))
185     {
186       lex_discard_line (r->lexer);
187       return false;
188     }
189
190   ds_assign_string (&r->line, lex_entire_line_ds (r->lexer) );
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       ds_chomp (&r->line, '\n');
212     }
213   else if (fh_get_mode (r->fh) == FH_MODE_BINARY)
214     {
215       size_t record_width = fh_get_record_width (r->fh);
216       size_t amt = ds_read_stream (&r->line, 1, record_width, r->file);
217       if (record_width != amt)
218         {
219           if (ferror (r->file))
220             msg (ME, _("Error reading file %s: %s."),
221                  fh_get_name (r->fh), strerror (errno));
222           else if (amt != 0)
223             msg (ME, _("%s: Partial record at end of file."),
224                  fh_get_name (r->fh));
225
226           return false;
227         }
228     }
229   else
230     NOT_REACHED ();
231
232   r->where.line_number++;
233
234   return true;
235 }
236
237 /* Reads a record from R, setting the current position to the
238    start of the line.  If an error occurs or end-of-file is
239    encountered, the current line is set to null. */
240 static bool
241 read_record (struct dfm_reader *r)
242 {
243   return (fh_get_referent (r->fh) == FH_REF_FILE
244           ? read_file_record (r)
245           : read_inline_record (r));
246 }
247
248 /* Returns the number of attempts, thus far, to advance past
249    end-of-file in reader R.  Reads forward in HANDLE's file, if
250    necessary, to find out.
251
252    Normally, the user stops attempting to read from the file the
253    first time EOF is reached (a return value of 1).  If the user
254    tries to read past EOF again (a return value of 2 or more),
255    an error message is issued, and the caller should more
256    forcibly abort to avoid an infinite loop. */
257 unsigned
258 dfm_eof (struct dfm_reader *r)
259 {
260   if (r->flags & DFM_ADVANCE)
261     {
262       r->flags &= ~DFM_ADVANCE;
263
264       if (r->eof_cnt == 0 && read_record (r) )
265         {
266           r->pos = 0;
267           return 0;
268         }
269
270       r->eof_cnt++;
271       if (r->eof_cnt == 2)
272         {
273           if (r->fh != fh_inline_file ())
274             msg (ME, _("Attempt to read beyond end-of-file on file %s."),
275                  fh_get_name (r->fh));
276           else
277             msg (ME, _("Attempt to read beyond END DATA."));
278         }
279     }
280
281   return r->eof_cnt;
282 }
283
284 /* Returns the current record in the file corresponding to
285    HANDLE.  Aborts if reading from the file is necessary or at
286    end of file, so call dfm_eof() first. */
287 struct substring
288 dfm_get_record (struct dfm_reader *r)
289 {
290   assert ((r->flags & DFM_ADVANCE) == 0);
291   assert (r->eof_cnt == 0);
292
293   return ds_substr (&r->line, r->pos, SIZE_MAX);
294 }
295
296 /* Expands tabs in the current line into the equivalent number of
297    spaces, if appropriate for this kind of file.  Aborts if
298    reading from the file is necessary or at end of file, so call
299    dfm_eof() first.*/
300 void
301 dfm_expand_tabs (struct dfm_reader *r)
302 {
303   size_t ofs, new_pos, tab_width;
304
305   assert ((r->flags & DFM_ADVANCE) == 0);
306   assert (r->eof_cnt == 0);
307
308   if (r->flags & DFM_TABS_EXPANDED)
309     return;
310   r->flags |= DFM_TABS_EXPANDED;
311
312   if (r->fh != fh_inline_file ()
313       && (fh_get_mode (r->fh) == FH_MODE_BINARY
314           || fh_get_tab_width (r->fh) == 0
315           || ds_find_char (&r->line, '\t') == SIZE_MAX))
316     return;
317
318   /* Expand tabs from r->line into r->scratch, and figure out
319      new value for r->pos. */
320   tab_width = fh_get_tab_width (r->fh);
321   ds_clear (&r->scratch);
322   new_pos = SIZE_MAX;
323   for (ofs = 0; ofs < ds_length (&r->line); ofs++)
324     {
325       unsigned char c;
326
327       if (ofs == r->pos)
328         new_pos = ds_length (&r->scratch);
329
330       c = ds_data (&r->line)[ofs];
331       if (c != '\t')
332         ds_put_char (&r->scratch, c);
333       else
334         {
335           do
336             ds_put_char (&r->scratch, ' ');
337           while (ds_length (&r->scratch) % tab_width != 0);
338         }
339     }
340   if (new_pos == SIZE_MAX)
341     {
342       /* Maintain the same relationship between position and line
343          length that we had before.  DATA LIST uses a
344          beyond-the-end position to deal with an empty field at
345          the end of the line. */
346       assert (r->pos >= ds_length (&r->line));
347       new_pos = (r->pos - ds_length (&r->line)) + ds_length (&r->scratch);
348     }
349
350   /* Swap r->line and r->scratch and set new r->pos. */
351   ds_swap (&r->line, &r->scratch);
352   r->pos = new_pos;
353 }
354
355 /* Causes dfm_get_record() or dfm_get_whole_record() to read in
356    the next record the next time it is executed on file
357    HANDLE. */
358 void
359 dfm_forward_record (struct dfm_reader *r)
360 {
361   r->flags |= DFM_ADVANCE;
362 }
363
364 /* Cancels the effect of any previous dfm_fwd_record() executed
365    on file HANDLE.  Sets the current line to begin in the 1-based
366    column COLUMN.  */
367 void
368 dfm_reread_record (struct dfm_reader *r, size_t column)
369 {
370   r->flags &= ~DFM_ADVANCE;
371   r->pos = MAX (column, 1) - 1;
372 }
373
374 /* Sets the current line to begin COLUMNS characters following
375    the current start. */
376 void
377 dfm_forward_columns (struct dfm_reader *r, size_t columns)
378 {
379   dfm_reread_record (r, (r->pos + 1) + columns);
380 }
381
382 /* Returns the 1-based column to which the line pointer in HANDLE
383    is set.  Unless dfm_reread_record() or dfm_forward_columns()
384    have been called, this is 1. */
385 size_t
386 dfm_column_start (const struct dfm_reader *r)
387 {
388   return r->pos + 1;
389 }
390
391 /* Returns the number of columns we are currently beyond the end
392    of the line.  At or before end-of-line, this is 0; one column
393    after end-of-line, this is 1; and so on. */
394 size_t
395 dfm_columns_past_end (const struct dfm_reader *r)
396 {
397   return r->pos < ds_length (&r->line) ? 0 : ds_length (&r->line) - r->pos;
398 }
399
400 /* Returns the 1-based column within the current line that P
401    designates. */
402 size_t
403 dfm_get_column (const struct dfm_reader *r, const char *p)
404 {
405   return ds_pointer_to_position (&r->line, p) + 1;
406 }
407
408 /* Pushes the file name and line number on the fn/ln stack. */
409 void
410 dfm_push (struct dfm_reader *r)
411 {
412   if (r->fh != fh_inline_file ())
413     msg_push_msg_locator (&r->where);
414 }
415
416 /* Pops the file name and line number from the fn/ln stack. */
417 void
418 dfm_pop (struct dfm_reader *r)
419 {
420   if (r->fh != fh_inline_file ())
421     msg_pop_msg_locator (&r->where);
422 }
423 \f
424 /* BEGIN DATA...END DATA procedure. */
425
426 /* Perform BEGIN DATA...END DATA as a procedure in itself. */
427 int
428 cmd_begin_data (struct lexer *lexer, struct dataset *ds)
429 {
430   struct dfm_reader *r;
431   bool ok;
432
433   if (!fh_is_open (fh_inline_file ()))
434     {
435       msg (SE, _("This command is not valid here since the current "
436                  "input program does not access the inline file."));
437       return CMD_CASCADING_FAILURE;
438     }
439
440   /* Open inline file. */
441   r = dfm_open_reader (fh_inline_file (), lexer);
442   r->flags |= DFM_SAW_BEGIN_DATA;
443
444   /* Input procedure reads from inline file. */
445   prompt_set_style (PROMPT_DATA);
446   casereader_destroy (proc_open (ds));
447   ok = proc_commit (ds);
448   dfm_close_reader (r);
449
450   return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
451 }