Adopt use of gnulib for portability.
[pspp-builds.git] / src / dfm-read.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-2004 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 "dfm-read.h"
22 #include <ctype.h>
23 #include <errno.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include "alloc.h"
27 #include "command.h"
28 #include "error.h"
29 #include "file-handle.h"
30 #include "filename.h"
31 #include "getl.h"
32 #include "lexer.h"
33 #include "str.h"
34 #include "vfm.h"
35
36 #include "gettext.h"
37 #define _(msgid) gettext (msgid)
38
39 #include "debug-print.h"
40
41 /* Flags for DFM readers. */
42 enum dfm_reader_flags
43   {
44     DFM_EOF = 001,              /* At end-of-file? */
45     DFM_ADVANCE = 002,          /* Read next line on dfm_get_record() call? */
46     DFM_SAW_BEGIN_DATA = 004,   /* For inline_file only, whether we've 
47                                    already read a BEGIN DATA line. */
48     DFM_TABS_EXPANDED = 010,    /* Tabs have been expanded. */
49   };
50
51 /* Data file reader. */
52 struct dfm_reader
53   {
54     struct file_handle *fh;     /* File handle. */
55     struct file_ext file;       /* Associated file. */
56     struct file_locator where;  /* Current location in data file. */
57     struct string line;         /* Current line. */
58     size_t pos;                 /* Offset in line of current character. */
59     struct string scratch;      /* Extra line buffer. */
60     enum dfm_reader_flags flags; /* Zero or more of DFM_*. */
61   };
62
63 static int inline_open_cnt;
64 static struct dfm_reader *inline_file;
65
66 static void read_record (struct dfm_reader *r);
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
74   if (r == NULL)
75     return;
76
77   if (r->fh != NULL) 
78     still_open = fh_close (r->fh, "data file", "rs");
79   else
80     {
81       assert (inline_open_cnt > 0);
82       still_open = --inline_open_cnt;
83
84       if (!still_open) 
85         {
86           /* Skip any remaining data on the inline file. */
87           if (r->flags & DFM_SAW_BEGIN_DATA)
88             while ((r->flags & DFM_EOF) == 0)
89               read_record (r);
90           inline_file = NULL;
91         }
92     }
93   if (still_open)
94     return;
95
96   if (r->fh != NULL && r->file.file)
97     {
98       fn_close_ext (&r->file);
99       free (r->file.filename);
100       r->file.filename = NULL;
101     }
102   ds_destroy (&r->line);
103   ds_destroy (&r->scratch);
104   free (r);
105 }
106
107 /* Opens the file designated by file handle FH for reading as a
108    data file.  Providing a null pointer for FH designates the
109    "inline file", that is, data included inline in the command
110    file between BEGIN FILE and END FILE.  Returns nonzero only if
111    successful. */
112 struct dfm_reader *
113 dfm_open_reader (struct file_handle *fh)
114 {
115   struct dfm_reader *r;
116   void **rp;
117
118   if (fh != NULL) 
119     {
120       rp = fh_open (fh, "data file", "rs");
121       if (rp == NULL)
122         return NULL;
123       if (*rp != NULL)
124         return *rp; 
125     }
126   else 
127     {
128       assert (inline_open_cnt >= 0);
129       if (inline_open_cnt++ > 0)
130         return inline_file;
131       rp = NULL;
132     }
133   
134   r = xmalloc (sizeof *r);
135   r->fh = fh;
136   if (fh != NULL) 
137     {
138       r->where.filename = handle_get_filename (fh);
139       r->where.line_number = 0; 
140     }
141   r->file.file = NULL;
142   ds_init (&r->line, 64);
143   ds_init (&r->scratch, 0);
144   r->flags = DFM_ADVANCE;
145
146   if (fh != NULL)
147     {
148       r->file.filename = xstrdup (handle_get_filename (r->fh));
149       r->file.mode = "rb";
150       r->file.file = NULL;
151       r->file.sequence_no = NULL;
152       r->file.param = NULL;
153       r->file.postopen = NULL;
154       r->file.preclose = NULL;
155       if (!fn_open_ext (&r->file))
156         {
157           msg (ME, _("Could not open \"%s\" for reading "
158                      "as a data file: %s."),
159                handle_get_filename (r->fh), strerror (errno));
160           err_cond_fail ();
161           fh_close (fh,"data file", "rs");
162           free (r);
163           return NULL;
164         }
165       *rp = r;
166     }
167   else
168     inline_file = r;
169
170   return r;
171 }
172
173 static int
174 read_inline_record (struct dfm_reader *r)
175 {
176   if ((r->flags & DFM_SAW_BEGIN_DATA) == 0)
177     {
178       char *s;
179
180       r->flags |= DFM_SAW_BEGIN_DATA;
181
182       /* FIXME: WTF can't this just be done with tokens?
183          Is this really a special case? */
184       do
185         {
186           char *cp;
187
188           if (!getl_read_line ())
189             {
190               msg (SE, _("BEGIN DATA expected."));
191               err_failure ();
192             }
193
194           /* Skip leading whitespace, separate out first
195              word, so that S points to a single word reduced
196              to lowercase. */
197           s = ds_c_str (&getl_buf);
198           while (isspace ((unsigned char) *s))
199             s++;
200           for (cp = s; isalpha ((unsigned char) *cp); cp++)
201             *cp = tolower ((unsigned char) (*cp));
202           ds_truncate (&getl_buf, cp - s);
203         }
204       while (*s == '\0');
205
206       if (!lex_id_match_len ("begin", 5, s, strcspn (s, " \t\r\v\n")))
207         {
208           msg (SE, _("BEGIN DATA expected."));
209           lex_preprocess_line ();
210           return 0;
211         }
212       getl_prompt = GETL_PRPT_DATA;
213     }
214       
215   if (!getl_read_line ())
216     {
217       msg (SE, _("Unexpected end-of-file while reading data in BEGIN "
218                  "DATA.  This probably indicates "
219                  "a missing or misformatted END DATA command.  "
220                  "END DATA must appear by itself on a single line "
221                  "with exactly one space between words."));
222       err_failure ();
223     }
224
225   if (r->fh != NULL)
226     r->where.line_number++;
227
228   if (ds_length (&getl_buf) >= 8
229       && !strncasecmp (ds_c_str (&getl_buf), "end data", 8))
230     {
231       lex_set_prog (ds_c_str (&getl_buf) + ds_length (&getl_buf));
232       return 0;
233     }
234
235   ds_replace (&r->line, ds_c_str (&getl_buf));
236   return 1;
237 }
238
239 static int
240 read_file_record (struct dfm_reader *r)
241 {
242   assert (r->fh != NULL);
243   if (handle_get_mode (r->fh) == MODE_TEXT)
244     {
245       ds_clear (&r->line);
246       if (!ds_gets (&r->line, r->file.file)) 
247         {
248           if (ferror (r->file.file))
249             {
250               msg (ME, _("Error reading file %s: %s."),
251                    handle_get_name (r->fh), strerror (errno));
252               err_cond_fail ();
253             }
254           return 0;
255         }
256     }
257   else if (handle_get_mode (r->fh) == MODE_BINARY)
258     {
259       size_t record_width = handle_get_record_width (r->fh);
260       size_t amt;
261
262       if (ds_length (&r->line) < record_width) 
263         ds_rpad (&r->line, record_width, 0);
264           
265       amt = fread (ds_c_str (&r->line), 1, record_width,
266                    r->file.file);
267       if (record_width != amt)
268         {
269           if (ferror (r->file.file))
270             msg (ME, _("Error reading file %s: %s."),
271                  handle_get_name (r->fh), strerror (errno));
272           else if (amt != 0)
273             msg (ME, _("%s: Partial record at end of file."),
274                  handle_get_name (r->fh));
275           else
276             return 0;
277
278           err_cond_fail ();
279           return 0;
280         }
281     }
282   else
283     assert (0);
284
285   r->where.line_number++;
286
287   return 1;
288 }
289
290 /* Reads a record from R, setting the current position to the
291    start of the line.  If an error occurs or end-of-file is
292    encountered, the current line is set to null. */
293 static void
294 read_record (struct dfm_reader *r)
295 {
296   int success = r->fh != NULL ? read_file_record (r) : read_inline_record (r);
297   if (success)
298     r->pos = 0;
299   else
300     r->flags |= DFM_EOF;
301 }
302
303 /* Returns nonzero if end of file has been reached on HANDLE.
304    Reads forward in HANDLE's file, if necessary to tell. */
305 int
306 dfm_eof (struct dfm_reader *r) 
307 {
308   if (r->flags & DFM_ADVANCE)
309     {
310       r->flags &= ~DFM_ADVANCE;
311       if ((r->flags & DFM_EOF) == 0)
312         read_record (r);
313       else
314         {
315           if (r->fh != NULL)
316             msg (SE, _("Attempt to read beyond end-of-file on file %s."),
317                  handle_get_name (r->fh));
318           else
319             msg (SE, _("Attempt to read beyond END DATA."));
320           err_cond_fail ();
321         }
322     }
323
324   return (r->flags & DFM_EOF) != 0;
325 }
326
327 /* Returns the current record in the file corresponding to
328    HANDLE.  Aborts if reading from the file is necessary or at
329    end of file, so call dfm_eof() first.  Sets *LINE to the line,
330    which is not null-terminated.  The caller must not free or
331    modify the returned string.  */
332 void
333 dfm_get_record (struct dfm_reader *r, struct fixed_string *line)
334 {
335   assert ((r->flags & DFM_ADVANCE) == 0);
336   assert ((r->flags & DFM_EOF) == 0);
337   assert (r->pos <= ds_length (&r->line));
338
339   line->string = ds_data (&r->line) + r->pos;
340   line->length = ds_length (&r->line) - r->pos;
341 }
342
343 /* Expands tabs in the current line into the equivalent number of
344    spaces, if appropriate for this kind of file.  Aborts if
345    reading from the file is necessary or at end of file, so call
346    dfm_eof() first.*/
347 void
348 dfm_expand_tabs (struct dfm_reader *r) 
349 {
350   struct string temp;
351   size_t ofs, new_pos, tab_width;
352
353   assert ((r->flags & DFM_ADVANCE) == 0);
354   assert ((r->flags & DFM_EOF) == 0);
355   assert (r->pos <= ds_length (&r->line));
356
357   if (r->flags & DFM_TABS_EXPANDED)
358     return;
359   r->flags |= DFM_TABS_EXPANDED;
360
361   if (r->fh != NULL
362       && (handle_get_mode (r->fh) == MODE_BINARY
363           || handle_get_tab_width (r->fh) == 0
364           || memchr (ds_c_str (&r->line), '\t', ds_length (&r->line)) == NULL))
365     return;
366
367   /* Expand tabs from r->line into r->scratch, and figure out
368      new value for r->pos. */
369   tab_width = r->fh != NULL ? handle_get_tab_width (r->fh) : 8;
370   ds_clear (&r->scratch);
371   new_pos = 0;
372   for (ofs = 0; ofs < ds_length (&r->line); ofs++)
373     {
374       unsigned char c;
375       
376       if (ofs == r->pos)
377         new_pos = ds_length (&r->scratch);
378
379       c = ds_c_str (&r->line)[ofs];
380       if (c != '\t')
381         ds_putc (&r->scratch, c);
382       else 
383         {
384           do
385             ds_putc (&r->scratch, ' ');
386           while (ds_length (&r->scratch) % tab_width != 0);
387         }
388     }
389
390   /* Swap r->line and r->scratch and set new r->pos. */
391   temp = r->line;
392   r->line = r->scratch;
393   r->scratch = temp;
394   r->pos = new_pos;
395 }
396
397 /* Causes dfm_get_record() to read in the next record the next time it
398    is executed on file HANDLE. */
399 void
400 dfm_forward_record (struct dfm_reader *r)
401 {
402   r->flags |= DFM_ADVANCE;
403 }
404
405 /* Cancels the effect of any previous dfm_fwd_record() executed
406    on file HANDLE.  Sets the current line to begin in the 1-based
407    column COLUMN.  */
408 void
409 dfm_reread_record (struct dfm_reader *r, size_t column)
410 {
411   r->flags &= ~DFM_ADVANCE;
412   if (column < 1)
413     r->pos = 0;
414   else if (column > ds_length (&r->line))
415     r->pos = ds_length (&r->line);
416   else
417     r->pos = column - 1;
418 }
419
420 /* Sets the current line to begin COLUMNS characters following
421    the current start. */
422 void
423 dfm_forward_columns (struct dfm_reader *r, size_t columns)
424 {
425   dfm_reread_record (r, (r->pos + 1) + columns);
426 }
427
428 /* Returns the 1-based column to which the line pointer in HANDLE
429    is set.  Unless dfm_reread_record() or dfm_forward_columns()
430    have been called, this is 1. */
431 size_t
432 dfm_column_start (struct dfm_reader *r)
433 {
434   return r->pos + 1;
435 }
436
437 /* Pushes the filename and line number on the fn/ln stack. */
438 void
439 dfm_push (struct dfm_reader *r)
440 {
441   if (r->fh != NULL)
442     err_push_file_locator (&r->where);
443 }
444
445 /* Pops the filename and line number from the fn/ln stack. */
446 void
447 dfm_pop (struct dfm_reader *r)
448 {
449   if (r->fh != NULL)
450     err_pop_file_locator (&r->where);
451 }
452 \f
453 /* BEGIN DATA...END DATA procedure. */
454
455 /* Perform BEGIN DATA...END DATA as a procedure in itself. */
456 int
457 cmd_begin_data (void)
458 {
459   struct dfm_reader *r;
460
461   /* FIXME: figure out the *exact* conditions, not these really
462      lenient conditions. */
463   if (vfm_source == NULL
464       || case_source_is_class (vfm_source, &storage_source_class))
465     {
466       msg (SE, _("This command is not valid here since the current "
467                  "input program does not access the inline file."));
468       err_cond_fail ();
469       return CMD_FAILURE;
470     }
471
472   /* Open inline file. */
473   r = dfm_open_reader (NULL);
474   r->flags |= DFM_SAW_BEGIN_DATA;
475
476   /* Input procedure reads from inline file. */
477   getl_prompt = GETL_PRPT_DATA;
478   procedure (NULL, NULL);
479
480   dfm_close_reader (r);
481
482   return CMD_SUCCESS;
483 }