1 /* PSPP - computes sample statistics.
2 Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3 Written by Ben Pfaff <blp@gnu.org>.
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.
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.
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
20 /* AIX requires this to be the first thing in the file. */
23 #define alloca __builtin_alloca
31 #ifndef alloca /* predefined by HP cc +Olibcalls */
45 #include "file-handle.h"
54 /*#define DEBUGGING 1*/
55 #include "debug-print.h"
57 /* file_handle extension structure. */
60 struct file_ext file; /* Associated file. */
62 char *line; /* Current line, not null-terminated. */
63 size_t len; /* Length of line. */
65 char *ptr; /* Pointer into line that is returned by
67 size_t size; /* Number of bytes allocated for line. */
68 int advance; /* Nonzero=dfm_get_record() reads a new
69 record; otherwise returns current record. */
72 /* These are defined at the end of this file. */
73 static struct fh_ext_class dfm_r_class;
74 static struct fh_ext_class dfm_w_class;
76 static void read_record (struct file_handle *h);
78 /* Internal (low level). */
80 /* Closes the file handle H which was opened by open_file_r() or
83 dfm_close (struct file_handle *h)
85 struct dfm_fhuser_ext *ext = h->ext;
87 /* Skip any remaining data on the inline file. */
89 while (ext->line != NULL)
92 msg (VM (2), _("%s: Closing data-file handle %s."),
93 fh_handle_filename (h), fh_handle_name (h));
94 assert (h->class == &dfm_r_class || h->class == &dfm_w_class);
97 fn_close_ext (&ext->file);
98 free (ext->file.filename);
99 ext->file.filename = NULL;
105 /* Initializes EXT properly as an inline data file. */
107 open_inline_file (struct dfm_fhuser_ext *ext)
109 /* We want to indicate that the file is open, that we are not at
110 eof, and that another line needs to be read in. */
112 memset (&ext->file, 0, sizeof ext->file);
114 ext->file.file = NULL;
115 ext->line = xmalloc (128);
117 strcpy (ext->line, _("<<Bug in dfm.c>>"));
119 ext->len = strlen (ext->line);
120 ext->ptr = ext->line;
125 /* Opens a file handle for reading as a data file. */
127 open_file_r (struct file_handle *h)
129 struct dfm_fhuser_ext ext;
131 h->where.line_number = 0;
132 ext.file.file = NULL;
139 msg (VM (1), _("%s: Opening data-file handle %s for reading."),
140 fh_handle_filename (h), fh_handle_name (h));
143 if (h == inline_file)
147 /* WTF can't this just be done with tokens?
148 Is this really a special case?
154 if (!getl_read_line ())
156 msg (SE, _("BEGIN DATA expected."));
160 /* Skip leading whitespace, separate out first word, so that
161 S points to a single word reduced to lowercase. */
162 s = ds_value (&getl_buf);
163 while (isspace ((unsigned char) *s))
165 for (cp = s; isalpha ((unsigned char) *cp); cp++)
166 *cp = tolower ((unsigned char) (*cp));
167 ds_truncate (&getl_buf, cp - s);
171 if (!lex_id_match_len ("begin", 5, s, strcspn (s, " \t\r\v\n")))
173 msg (SE, _("BEGIN DATA expected."));
175 lex_preprocess_line ();
178 getl_prompt = GETL_PRPT_DATA;
180 open_inline_file (&ext);
184 ext.file.filename = xstrdup (h->norm_fn);
185 ext.file.mode = "rb";
186 ext.file.file = NULL;
187 ext.file.sequence_no = NULL;
188 ext.file.param = NULL;
189 ext.file.postopen = NULL;
190 ext.file.preclose = NULL;
191 if (!fn_open_ext (&ext.file))
193 msg (ME, _("An error occurred while opening \"%s\" for reading "
194 "as a data file: %s."), h->fn, strerror (errno));
200 h->class = &dfm_r_class;
201 h->ext = xmalloc (sizeof (struct dfm_fhuser_ext));
202 memcpy (h->ext, &ext, sizeof (struct dfm_fhuser_ext));
207 /* Opens a file handle for writing as a data file. */
209 open_file_w (struct file_handle *h)
211 struct dfm_fhuser_ext ext;
213 ext.file.file = NULL;
220 h->where.line_number = 0;
222 msg (VM (1), _("%s: Opening data-file handle %s for writing."),
223 fh_handle_filename (h), fh_handle_name (h));
226 if (h == inline_file)
228 msg (ME, _("Cannot open the inline file for writing."));
233 ext.file.filename = xstrdup (h->norm_fn);
234 ext.file.mode = "wb";
235 ext.file.file = NULL;
236 ext.file.sequence_no = NULL;
237 ext.file.param = NULL;
238 ext.file.postopen = NULL;
239 ext.file.preclose = NULL;
241 if (!fn_open_ext (&ext.file))
243 msg (ME, _("An error occurred while opening \"%s\" for writing "
244 "as a data file: %s."), h->fn, strerror (errno));
249 h->class = &dfm_w_class;
250 h->ext = xmalloc (sizeof (struct dfm_fhuser_ext));
251 memcpy (h->ext, &ext, sizeof (struct dfm_fhuser_ext));
256 /* Ensures that the line buffer in file handle with extension EXT is
257 big enough to hold a line of length EXT->LEN characters not
258 including null terminator. */
259 #define force_line_buffer_expansion() \
262 if (ext->len + 1 > ext->size) \
264 ext->size = ext->len * 2; \
265 ext->line = xrealloc (ext->line, ext->size); \
270 /* Counts the number of tabs in string STRING of length LEN. */
272 count_tabs (char *s, size_t len)
278 char *cp = memchr (s, '\t', len);
287 /* Converts all the tabs in H->EXT->LINE to an equivalent number of
288 spaces, if necessary. */
290 tabs_to_spaces (struct file_handle *h)
292 struct dfm_fhuser_ext *ext = h->ext;
294 char *first_tab; /* Location of first tab (if any). */
295 char *second_tab; /* Location of second tab (if any). */
296 size_t orig_len; /* Line length at function entry. */
298 /* If there aren't any tabs then there's nothing to do. */
299 first_tab = memchr (ext->line, '\t', ext->len);
300 if (first_tab == NULL)
304 /* If there's just one tab then expand it inline. Otherwise do a
305 full string copy to another buffer. */
306 second_tab = memchr (first_tab + 1, '\t',
307 ext->len - (first_tab - ext->line + 1));
308 if (second_tab == NULL)
310 int n_spaces = 8 - (first_tab - ext->line) % 8;
312 ext->len += n_spaces - 1;
314 /* Expand the line if necessary, keeping the first_tab pointer
317 size_t ofs = first_tab - ext->line;
318 force_line_buffer_expansion ();
319 first_tab = ext->line + ofs;
322 memmove (first_tab + n_spaces, first_tab + 1,
323 orig_len - (first_tab - ext->line + 1));
324 memset (first_tab, ' ', n_spaces);
326 /* Make a local copy of original text. */
327 char *orig_line = local_alloc (ext->len + 1);
328 memcpy (orig_line, ext->line, ext->len);
330 /* Allocate memory assuming we need to add 8 spaces for every tab. */
331 ext->len += 2 + count_tabs (second_tab + 1,
332 ext->len - (second_tab - ext->line + 1));
334 /* Expand the line if necessary, keeping the first_tab pointer
337 size_t ofs = first_tab - ext->line;
338 force_line_buffer_expansion ();
339 first_tab = ext->line + ofs;
342 /* Walk through orig_line, expanding tabs into ext->line. */
344 char *src_p = orig_line + (first_tab - ext->line);
345 char *dest_p = first_tab;
347 for (; src_p < orig_line + orig_len; src_p++)
349 /* Most characters simply pass through untouched. */
356 /* Tabs are expanded into an equivalent number of
359 int n_spaces = 8 - (dest_p - ext->line) % 8;
361 memset (dest_p, ' ', n_spaces);
366 /* Supply null terminator and actual string length. */
368 ext->len = dest_p - ext->line;
371 local_free (orig_line);
375 /* Reads a record from H->EXT->FILE into H->EXT->LINE, setting
376 H->EXT->PTR to H->EXT->LINE, and setting H->EXT-LEN to the length
377 of the line. The line is not null-terminated. If an error occurs
378 or end-of-file is encountered, H->EXT->LINE is set to NULL. */
380 read_record (struct file_handle *h)
382 struct dfm_fhuser_ext *ext = h->ext;
384 if (h == inline_file)
386 if (!getl_read_line ())
388 msg (SE, _("Unexpected end-of-file while reading data in BEGIN "
389 "DATA. This probably indicates "
390 "a missing or misformatted END DATA command. "
391 "END DATA must appear by itself on a single line "
392 "with exactly one space between words."));
396 h->where.line_number++;
398 if (ds_length (&getl_buf) >= 8
399 && !strncasecmp (ds_value (&getl_buf), "end data", 8))
401 lex_set_prog (ds_value (&getl_buf) + ds_length (&getl_buf));
405 ext->len = ds_length (&getl_buf);
406 force_line_buffer_expansion ();
407 strcpy (ext->line, ds_value (&getl_buf));
411 if (h->recform == FH_RF_VARIABLE)
413 /* PORTME: here you should adapt the routine to your
414 system's concept of a "line" of text. */
415 int read_len = getline (&ext->line, &ext->size, ext->file.file);
419 if (ferror (ext->file.file))
421 msg (ME, _("Error reading file %s: %s."),
422 fh_handle_name (h), strerror (errno));
427 ext->len = (size_t) read_len;
429 else if (h->recform == FH_RF_FIXED)
433 if (ext->size < h->lrecl)
435 ext->size = h->lrecl;
436 ext->line = xmalloc (ext->size);
438 amt = fread (ext->line, 1, h->lrecl, ext->file.file);
441 if (ferror (ext->file.file))
442 msg (ME, _("Error reading file %s: %s."),
443 fh_handle_name (h), strerror (errno));
445 msg (ME, _("%s: Partial record at end of file."),
457 h->where.line_number++;
460 /* Strip trailing whitespace, I forget why. But there's a good
461 reason, I'm sure. I'm too scared to eliminate this code. */
462 if (h->recform == FH_RF_VARIABLE)
464 while (ext->len && isspace ((unsigned char) ext->line[ext->len - 1]))
467 /* Convert tabs to spaces. */
470 ext->ptr = ext->line;
475 /*hit eof or an error, clean up everything. */
479 ext->line = ext->ptr = NULL;
483 /* Public (high level). */
485 /* Returns the current record in the file corresponding to HANDLE.
486 Opens files and reads records, etc., as necessary. Sets *LEN to
487 the length of the line. The line returned is not null-terminated.
488 Returns NULL at end of file. Calls fail() on attempt to read past
491 dfm_get_record (struct file_handle *h, int *len)
493 if (h->class == NULL)
495 if (!open_file_r (h))
499 else if (h->class != &dfm_r_class)
501 msg (ME, _("Cannot read from file %s already opened for %s."),
502 fh_handle_name (h), gettext (h->class->name));
507 struct dfm_fhuser_ext *ext = h->ext;
515 msg (SE, _("Attempt to read beyond end-of-file on file %s."),
523 struct dfm_fhuser_ext *ext = h->ext;
529 *len = ext->len - (ext->ptr - ext->line);
537 /* Come here on reading beyond eof or reading from a file already
538 open for something else. */
544 /* Causes dfm_get_record() to read in the next record the next time it
545 is executed on file HANDLE. */
547 dfm_fwd_record (struct file_handle *h)
549 struct dfm_fhuser_ext *ext = h->ext;
551 assert (h->class == &dfm_r_class);
555 /* Cancels the effect of any previous dfm_fwd_record() executed on
556 file HANDLE. Sets the current line to begin in the 1-based column
557 COLUMN, as with dfm_set_record but based on a column number instead
558 of a character pointer. */
560 dfm_bkwd_record (struct file_handle *h, int column)
562 struct dfm_fhuser_ext *ext = h->ext;
564 assert (h->class == &dfm_r_class);
566 ext->ptr = ext->line + min ((int) ext->len + 1, column) - 1;
569 /* Sets the current line in HANDLE to NEW_LINE, which must point
570 somewhere in the line last returned by dfm_get_record(). Used by
571 DATA LIST FREE to strip the leading portion off the current line. */
573 dfm_set_record (struct file_handle *h, char *new_line)
575 struct dfm_fhuser_ext *ext = h->ext;
577 assert (h->class == &dfm_r_class);
581 /* Returns the 0-based current column to which the line pointer in
582 HANDLE is set. Unless dfm_set_record() or dfm_bkwd_record() have
583 been called, this is 0. */
585 dfm_get_cur_col (struct file_handle *h)
587 struct dfm_fhuser_ext *ext = h->ext;
589 assert (h->class == &dfm_r_class);
590 return ext->ptr - ext->line;
593 /* Writes record REC having length LEN to the file corresponding to
594 HANDLE. REC is not null-terminated. Returns nonzero on success,
597 dfm_put_record (struct file_handle *h, const char *rec, size_t len)
602 if (h->class == NULL)
604 if (!open_file_w (h))
607 else if (h->class != &dfm_w_class)
609 msg (ME, _("Cannot write to file %s already opened for %s."),
610 fh_handle_name (h), gettext (h->class->name));
615 if (h->recform == FH_RF_FIXED && len < h->lrecl)
620 ptr = local_alloc (amt);
621 memcpy (ptr, rec, len);
622 ch = h->mode == FH_MD_CHARACTER ? ' ' : 0;
623 memset (&ptr[len], ch, amt - len);
631 if (1 != fwrite (ptr, amt, 1, ((struct dfm_fhuser_ext *) h->ext)->file.file))
633 msg (ME, _("Error writing file %s: %s."), fh_handle_name (h),
645 /* Pushes the filename and line number on the fn/ln stack. */
647 dfm_push (struct file_handle *h)
649 if (h != inline_file)
650 err_push_file_locator (&h->where);
653 /* Pops the filename and line number from the fn/ln stack. */
655 dfm_pop (struct file_handle *h)
657 if (h != inline_file)
658 err_pop_file_locator (&h->where);
661 /* BEGIN DATA...END DATA procedure. */
663 /* Perform BEGIN DATA...END DATA as a procedure in itself. */
665 cmd_begin_data (void)
667 struct dfm_fhuser_ext *ext;
669 /* FIXME: figure out the *exact* conditions, not these really
670 lenient conditions. */
671 if (vfm_source == NULL
672 || vfm_source == &vfm_memory_stream
673 || vfm_source == &vfm_disk_stream
674 || vfm_source == &sort_stream)
676 msg (SE, _("This command is not valid here since the current "
677 "input program does not access the inline file."));
682 /* Initialize inline_file. */
683 msg (VM (1), _("inline file: Opening for reading."));
684 inline_file->class = &dfm_r_class;
685 inline_file->ext = xmalloc (sizeof (struct dfm_fhuser_ext));
686 open_inline_file (inline_file->ext);
688 /* We don't actually read from the inline file. The input procedure
689 is what reads from it. */
690 getl_prompt = GETL_PRPT_DATA;
691 procedure (NULL, NULL, NULL);
693 ext = inline_file->ext;
695 if (ext && ext->line)
697 msg (MW, _("Skipping remaining inline data."));
698 for (read_record (inline_file); ext->line; read_record (inline_file))
701 assert (inline_file->ext == NULL);
706 static struct fh_ext_class dfm_r_class =
709 N_("reading as a data file"),
713 static struct fh_ext_class dfm_w_class =
716 N_("writing as a data file"),