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