GNU standards require "file name" instead of "filename" in
[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 #include <language/data-io/data-reader.h>
22 #include <ctype.h>
23 #include <errno.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <libpspp/alloc.h>
27 #include <language/command.h>
28 #include <libpspp/message.h>
29 #include <language/data-io/file-handle.h>
30 #include <data/file-handle-def.h>
31 #include <data/file-name.h>
32 #include <language/line-buffer.h>
33 #include <language/lexer/lexer.h>
34 #include <libpspp/str.h>
35 #include <procedure.h>
36
37 #include "gettext.h"
38 #define _(msgid) gettext (msgid)
39
40 /* Flags for DFM readers. */
41 enum dfm_reader_flags
42   {
43     DFM_ADVANCE = 002,          /* Read next line on dfm_get_record() call? */
44     DFM_SAW_BEGIN_DATA = 004,   /* For inline_file only, whether we've 
45                                    already read a BEGIN DATA line. */
46     DFM_TABS_EXPANDED = 010,    /* Tabs have been expanded. */
47   };
48
49 /* Data file reader. */
50 struct dfm_reader
51   {
52     struct file_handle *fh;     /* File handle. */
53     struct file_locator where;  /* Current location in data file. */
54     struct string line;         /* Current line. */
55     struct string scratch;      /* Extra line buffer. */
56     enum dfm_reader_flags flags; /* Zero or more of DFM_*. */
57     FILE *file;                 /* Associated file. */
58     size_t pos;                 /* Offset in line of current character. */
59     unsigned eof_cnt;           /* # of attempts to advance past EOF. */
60   };
61
62 /* Closes reader R opened by dfm_open_reader(). */
63 void
64 dfm_close_reader (struct dfm_reader *r)
65 {
66   int still_open;
67   bool is_inline;
68   char *file_name;
69
70   if (r == NULL)
71     return;
72
73   is_inline = r->fh == fh_inline_file ();
74   file_name = is_inline ? NULL : xstrdup (fh_get_filename (r->fh));
75   still_open = fh_close (r->fh, "data file", "rs");
76   if (still_open) 
77     {
78       free (file_name);
79       return; 
80     }
81
82   if (!is_inline)
83     fn_close (file_name, r->file);
84   else
85     {
86       /* Skip any remaining data on the inline file. */
87       if (r->flags & DFM_SAW_BEGIN_DATA) 
88         {
89           dfm_reread_record (r, 0);
90           while (!dfm_eof (r))
91             dfm_forward_record (r); 
92         }
93     }
94
95   ds_destroy (&r->line);
96   ds_destroy (&r->scratch);
97   free (r);
98   free (file_name);
99 }
100
101 /* Opens the file designated by file handle FH for reading as a
102    data file.  Providing fh_inline_file() for FH designates the
103    "inline file", that is, data included inline in the command
104    file between BEGIN FILE and END FILE.  Returns a reader if
105    successful, or a null pointer otherwise. */
106 struct dfm_reader *
107 dfm_open_reader (struct file_handle *fh)
108 {
109   struct dfm_reader *r;
110   void **rp;
111
112   rp = fh_open (fh, FH_REF_FILE | FH_REF_INLINE, "data file", "rs");
113   if (rp == NULL)
114     return NULL;
115   if (*rp != NULL)
116     return *rp; 
117   
118   r = xmalloc (sizeof *r);
119   r->fh = fh;
120   ds_init (&r->line, 64);
121   ds_init (&r->scratch, 0);
122   r->flags = DFM_ADVANCE;
123   r->eof_cnt = 0;
124   if (fh != fh_inline_file ()) 
125     {
126       r->where.filename = fh_get_filename (fh);
127       r->where.line_number = 0; 
128       r->file = fn_open (fh_get_filename (fh), "rb");
129       if (r->file == NULL)
130         {
131           msg (ME, _("Could not open \"%s\" for reading as a data file: %s."),
132                fh_get_filename (r->fh), strerror (errno));
133           fh_close (fh,"data file", "rs");
134           free (r);
135           return NULL;
136         }
137     }
138   *rp = r;
139
140   return r;
141 }
142
143 /* Returns true if an I/O error occurred on READER, false otherwise. */
144 bool
145 dfm_reader_error (const struct dfm_reader *r) 
146 {
147   return fh_get_referent (r->fh) == FH_REF_FILE && ferror (r->file);
148 }
149
150 /* Reads a record from the inline file into R.
151    Returns true if successful, false on failure. */
152 static bool
153 read_inline_record (struct dfm_reader *r)
154 {
155   if ((r->flags & DFM_SAW_BEGIN_DATA) == 0)
156     {
157       r->flags |= DFM_SAW_BEGIN_DATA;
158
159       while (token == '.')
160         lex_get ();
161       if (!lex_force_match_id ("BEGIN") || !lex_force_match_id ("DATA"))
162         return false;
163       getl_set_prompt_style (GETL_PROMPT_DATA);
164     }
165       
166   if (!getl_read_line (NULL))
167     {
168       msg (SE, _("Unexpected end-of-file while reading data in BEGIN "
169                  "DATA.  This probably indicates "
170                  "a missing or misformatted END DATA command.  "
171                  "END DATA must appear by itself on a single line "
172                  "with exactly one space between words."));
173       return false;
174     }
175
176   if (ds_length (&getl_buf) >= 8
177       && !strncasecmp (ds_c_str (&getl_buf), "end data", 8))
178     {
179       lex_set_prog (ds_c_str (&getl_buf) + ds_length (&getl_buf));
180       return false;
181     }
182
183   ds_assign_string (&r->line, &getl_buf);
184   return true;
185 }
186
187 /* Reads a record from a disk file into R.
188    Returns true if successful, false on failure. */
189 static bool
190 read_file_record (struct dfm_reader *r)
191 {
192   assert (r->fh != fh_inline_file ());
193   if (fh_get_mode (r->fh) == FH_MODE_TEXT)
194     {
195       ds_clear (&r->line);
196       if (!ds_gets (&r->line, r->file)) 
197         {
198           if (ferror (r->file))
199             msg (ME, _("Error reading file %s: %s."),
200                  fh_get_name (r->fh), strerror (errno));
201           return false;
202         }
203     }
204   else if (fh_get_mode (r->fh) == FH_MODE_BINARY)
205     {
206       size_t record_width = fh_get_record_width (r->fh);
207       size_t amt;
208
209       if (ds_length (&r->line) < record_width) 
210         ds_rpad (&r->line, record_width, 0);
211           
212       amt = fread (ds_c_str (&r->line), 1, record_width, r->file);
213       if (record_width != amt)
214         {
215           if (ferror (r->file))
216             msg (ME, _("Error reading file %s: %s."),
217                  fh_get_name (r->fh), strerror (errno));
218           else if (amt != 0)
219             msg (ME, _("%s: Partial record at end of file."),
220                  fh_get_name (r->fh));
221
222           return false;
223         }
224     }
225   else
226     abort ();
227
228   r->where.line_number++;
229
230   return true;
231 }
232
233 /* Reads a record from R, setting the current position to the
234    start of the line.  If an error occurs or end-of-file is
235    encountered, the current line is set to null. */
236 static bool
237 read_record (struct dfm_reader *r)
238 {
239   return (fh_get_referent (r->fh) == FH_REF_FILE
240           ? read_file_record (r)
241           : read_inline_record (r));
242 }
243
244 /* Returns the number of attempts, thus far, to advance past
245    end-of-file in reader R.  Reads forward in HANDLE's file, if
246    necessary, to find out.
247
248    Normally, the user stops attempting to read from the file the
249    first time EOF is reached (a return value of 1).  If the user
250    tries to read past EOF again (a return value of 2 or more),
251    an error message is issued, and the caller should more
252    forcibly abort to avoid an infinite loop. */
253 unsigned
254 dfm_eof (struct dfm_reader *r) 
255 {
256   if (r->flags & DFM_ADVANCE)
257     {
258       r->flags &= ~DFM_ADVANCE;
259
260       if (r->eof_cnt == 0 && read_record (r)) 
261         {
262           r->pos = 0;
263           return 0; 
264         }
265
266       r->eof_cnt++;
267       if (r->eof_cnt == 2)
268         {
269           if (r->fh != fh_inline_file ())
270             msg (ME, _("Attempt to read beyond end-of-file on file %s."),
271                  fh_get_name (r->fh));
272           else
273             msg (ME, _("Attempt to read beyond END DATA."));
274         }
275     }
276
277   return r->eof_cnt;
278 }
279
280 /* Returns the current record in the file corresponding to
281    HANDLE.  Aborts if reading from the file is necessary or at
282    end of file, so call dfm_eof() first.  Sets *LINE to the line,
283    which is not null-terminated.  The caller must not free or
284    modify the returned string.  */
285 void
286 dfm_get_record (struct dfm_reader *r, struct fixed_string *line)
287 {
288   assert ((r->flags & DFM_ADVANCE) == 0);
289   assert (r->eof_cnt == 0);
290   assert (r->pos <= ds_length (&r->line));
291
292   line->string = ds_data (&r->line) + r->pos;
293   line->length = ds_length (&r->line) - r->pos;
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   struct string temp;
304   size_t ofs, new_pos, tab_width;
305
306   assert ((r->flags & DFM_ADVANCE) == 0);
307   assert (r->eof_cnt == 0);
308   assert (r->pos <= ds_length (&r->line));
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           || memchr (ds_c_str (&r->line), '\t', ds_length (&r->line)) == NULL))
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 = 0;
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_c_str (&r->line)[ofs];
333       if (c != '\t')
334         ds_putc (&r->scratch, c);
335       else 
336         {
337           do
338             ds_putc (&r->scratch, ' ');
339           while (ds_length (&r->scratch) % tab_width != 0);
340         }
341     }
342
343   /* Swap r->line and r->scratch and set new r->pos. */
344   temp = r->line;
345   r->line = r->scratch;
346   r->scratch = temp;
347   r->pos = new_pos;
348 }
349
350 /* Causes dfm_get_record() to read in the next record the next time it
351    is executed on file HANDLE. */
352 void
353 dfm_forward_record (struct dfm_reader *r)
354 {
355   r->flags |= DFM_ADVANCE;
356 }
357
358 /* Cancels the effect of any previous dfm_fwd_record() executed
359    on file HANDLE.  Sets the current line to begin in the 1-based
360    column COLUMN.  */
361 void
362 dfm_reread_record (struct dfm_reader *r, size_t column)
363 {
364   r->flags &= ~DFM_ADVANCE;
365   if (column < 1)
366     r->pos = 0;
367   else if (column > ds_length (&r->line))
368     r->pos = ds_length (&r->line);
369   else
370     r->pos = column - 1;
371 }
372
373 /* Sets the current line to begin COLUMNS characters following
374    the current start. */
375 void
376 dfm_forward_columns (struct dfm_reader *r, size_t columns)
377 {
378   dfm_reread_record (r, (r->pos + 1) + columns);
379 }
380
381 /* Returns the 1-based column to which the line pointer in HANDLE
382    is set.  Unless dfm_reread_record() or dfm_forward_columns()
383    have been called, this is 1. */
384 size_t
385 dfm_column_start (struct dfm_reader *r)
386 {
387   return r->pos + 1;
388 }
389
390 /* Pushes the filename and line number on the fn/ln stack. */
391 void
392 dfm_push (struct dfm_reader *r)
393 {
394   if (r->fh != fh_inline_file ())
395     err_push_file_locator (&r->where);
396 }
397
398 /* Pops the filename and line number from the fn/ln stack. */
399 void
400 dfm_pop (struct dfm_reader *r)
401 {
402   if (r->fh != fh_inline_file ())
403     err_pop_file_locator (&r->where);
404 }
405 \f
406 /* BEGIN DATA...END DATA procedure. */
407
408 /* Perform BEGIN DATA...END DATA as a procedure in itself. */
409 int
410 cmd_begin_data (void)
411 {
412   struct dfm_reader *r;
413   bool ok;
414
415   if (!fh_is_open (fh_inline_file ()))
416     {
417       msg (SE, _("This command is not valid here since the current "
418                  "input program does not access the inline file."));
419       return CMD_CASCADING_FAILURE;
420     }
421
422   /* Open inline file. */
423   r = dfm_open_reader (fh_inline_file ());
424   r->flags |= DFM_SAW_BEGIN_DATA;
425
426   /* Input procedure reads from inline file. */
427   getl_set_prompt_style (GETL_PROMPT_DATA);
428   ok = procedure (NULL, NULL);
429
430   dfm_close_reader (r);
431
432   return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
433 }