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