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