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