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