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