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