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