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., 51 Franklin Street, Fifth Floor, Boston, MA
28 #include "dictionary.h"
38 #define _(msgid) gettext (msgid)
40 #include "debug-print.h"
42 /* Describes one DO REPEAT macro. */
45 int type; /* 1=variable names, 0=any other. */
46 char id[LONG_NAME_LEN + 1]; /* Macro identifier. */
47 char **replacement; /* Macro replacement. */
48 struct repeat_entry *next;
51 /* List of macro identifiers. */
52 static struct repeat_entry *repeat_tab;
54 /* Number of substitutions for each macro. */
57 /* List of lines before it's actually assigned to a file. */
58 static struct getl_line_list *line_buf_head;
59 static struct getl_line_list *line_buf_tail;
61 static int parse_ids (struct repeat_entry *);
62 static int parse_numbers (struct repeat_entry *);
63 static int parse_strings (struct repeat_entry *);
64 static void clean_up (void);
65 static int internal_cmd_do_repeat (void);
70 if (internal_cmd_do_repeat ())
77 /* Garbage collects all the allocated memory that's no longer
82 struct repeat_entry *iter, *next;
90 if (iter->replacement)
92 for (i = 0; i < count; i++)
93 free (iter->replacement[i]);
94 free (iter->replacement);
102 /* Allocates & appends another record at the end of the line_buf_tail
107 struct getl_line_list *new = xmalloc (sizeof *new);
109 if (line_buf_head == NULL)
110 line_buf_head = line_buf_tail = new;
112 line_buf_tail = line_buf_tail->next = new;
115 /* Returns nonzero if KEYWORD appears beginning at CONTEXT. */
117 recognize_keyword (const char *context, const char *keyword)
119 const char *end = context;
120 while (isalpha ((unsigned char) *end))
122 return lex_id_match_len (keyword, strlen (keyword), context, end - context);
125 /* Does the real work of parsing the DO REPEAT command and its nested
128 internal_cmd_do_repeat (void)
130 /* Name of first DO REPEAT macro. */
131 char first_name[LONG_NAME_LEN + 1];
133 /* Current filename. */
134 const char *current_filename = NULL;
136 /* 1=Print lines after preprocessing. */
139 /* The first step is parsing the DO REPEAT command itself. */
141 line_buf_head = NULL;
144 struct repeat_entry *e;
145 struct repeat_entry *iter;
148 /* Get a stand-in variable name and make sure it's unique. */
149 if (!lex_force_id ())
151 for (iter = repeat_tab; iter; iter = iter->next)
152 if (!strcasecmp (iter->id, tokid))
154 msg (SE, _("Identifier %s is given twice."), tokid);
158 /* Make a new stand-in variable entry and link it into the
160 e = xmalloc (sizeof *e);
162 e->next = repeat_tab;
163 strcpy (e->id, tokid);
166 /* Skip equals sign. */
168 if (!lex_force_match ('='))
171 /* Get the details of the variable's possible values. */
174 result = parse_ids (e);
175 else if (lex_is_number ())
176 result = parse_numbers (e);
177 else if (token == T_STRING)
178 result = parse_strings (e);
187 /* If this is the first variable then it defines how many
188 replacements there must be; otherwise enforce this number of
193 strcpy (first_name, e->id);
195 else if (count != result)
197 msg (SE, _("There must be the same number of substitutions "
198 "for each dummy variable specified. Since there "
199 "were %d substitutions for %s, there must be %d "
200 "for %s as well, but %d were specified."),
201 count, first_name, count, e->id, result);
208 while (token != '.');
210 /* Read all the lines inside the DO REPEAT ... END REPEAT. */
216 if (!getl_read_line ())
217 msg (FE, _("Unexpected end of file."));
219 /* If the current file has changed then record the fact. */
224 getl_location (&curfn, &curln);
225 if (current_filename != curfn)
227 assert (curln > 0 && curfn != NULL);
230 line_buf_tail->len = -curln;
231 line_buf_tail->line = xstrdup (curfn);
232 current_filename = curfn;
236 /* FIXME? This code is not strictly correct, however if you
237 have begun a line with DO REPEAT or END REPEAT and it's
238 *not* a command name, then you are obviously *trying* to
239 break this mechanism. And you will. Also, the entire
240 command names must appear on a single line--they can't be
243 char *cp = ds_c_str (&getl_buf);
245 /* Skip leading indentors and any whitespace. */
246 if (*cp == '+' || *cp == '-' || *cp == '.')
248 while (isspace ((unsigned char) *cp))
251 /* Find END REPEAT. */
252 if (recognize_keyword (cp, "end"))
254 while (isalpha ((unsigned char) *cp))
256 while (isspace ((unsigned char) *cp))
258 if (recognize_keyword (cp, "repeat"))
264 while (isalpha ((unsigned char) *cp))
266 while (isspace ((unsigned char) *cp))
269 print = recognize_keyword (cp, "print");
274 else /* Find DO REPEAT. */
275 if (!strncasecmp (cp, "do", 2))
278 while (isspace ((unsigned char) *cp))
280 if (!strncasecmp (cp, "rep", 3))
286 line_buf_tail->len = ds_length (&getl_buf);
287 line_buf_tail->line = xmalloc (ds_length (&getl_buf) + 1);
288 memcpy (line_buf_tail->line,
289 ds_c_str (&getl_buf), ds_length (&getl_buf) + 1);
293 /* FIXME: For the moment we simply discard the contents of the END
294 REPEAT line. We should actually check for the PRINT specifier.
295 This can be done easier when we buffer entire commands instead of
296 doing it token by token; see TODO. */
299 /* Tie up the loose end of the chain. */
300 if (line_buf_head == NULL)
302 msg (SW, _("No commands in scope."));
305 line_buf_tail->next = NULL;
307 /* Make new variables. */
309 struct repeat_entry *iter;
310 for (iter = repeat_tab; iter; iter = iter->next)
314 for (i = 0; i < count; i++)
316 /* Note that if the variable already exists there is no
318 dict_create_var (default_dict, iter->replacement[i], 0);
323 /* Create the DO REPEAT virtual input file. */
325 struct getl_script *script = xmalloc (sizeof *script);
327 script->first_line = line_buf_head;
328 script->cur_line = NULL;
329 script->remaining_loops = count;
330 script->loop_index = -1;
331 script->macros = repeat_tab;
332 script->print = print;
334 getl_add_DO_REPEAT_file (script);
340 /* Parses a set of ids for DO REPEAT. */
342 parse_ids (struct repeat_entry * e)
348 e->replacement = NULL;
355 if (!parse_mixed_vars (&names, &nnames, PV_NONE))
358 e->replacement = xnrealloc (e->replacement,
359 nnames + n, sizeof *e->replacement);
360 for (i = 0; i < nnames; i++)
362 e->replacement[n + i] = xstrdup (names[i]);
368 while (token != '/' && token != '.');
373 /* Stores VALUE into *REPL. */
375 store_numeric (char **repl, long value)
377 *repl = xmalloc (INT_DIGITS + 1);
378 sprintf (*repl, "%ld", value);
381 /* Parses a list of numbers for DO REPEAT. */
383 parse_numbers (struct repeat_entry *e)
385 /* First and last numbers for TO, plus the step factor. */
388 /* Alias to e->replacement. */
391 /* Number of entries in array; maximum number for this allocation
397 e->replacement = array = NULL;
401 /* Parse A TO B into a, b. */
402 if (!lex_force_int ())
410 if (!lex_force_int ())
418 if (n + (abs (b - a) + 1) > m)
420 m = n + (abs (b - a) + 1) + 16;
421 e->replacement = array = xnrealloc (array,
422 m, sizeof *e->replacement);
426 store_numeric (&array[n++], a);
432 for (iter = a; iter <= b; iter++)
433 store_numeric (&array[n++], iter);
435 for (iter = a; iter >= b; iter--)
436 store_numeric (&array[n++], iter);
441 while (token != '/' && token != '.');
442 e->replacement = xrealloc (array, n * sizeof *e->replacement);
447 /* Parses a list of strings for DO REPEAT. */
449 parse_strings (struct repeat_entry * e)
455 string = e->replacement = NULL;
460 if (token != T_STRING)
463 msg (SE, _("String expected."));
464 for (i = 0; i < n; i++)
473 e->replacement = string = xnrealloc (string,
474 m, sizeof *e->replacement);
476 string[n++] = lex_token_representation ();
481 while (token != '/' && token != '.');
482 e->replacement = xnrealloc (string, n, sizeof *e->replacement);
488 cmd_end_repeat (void)
490 msg (SE, _("No matching DO REPEAT."));
494 /* Finds a DO REPEAT macro with name MACRO_NAME and returns the
495 appropriate subsitution if found, or NULL if not. */
497 find_DO_REPEAT_substitution (char *macro_name)
499 struct getl_script *s;
501 for (s = getl_head; s; s = s->included_from)
503 struct repeat_entry *e;
505 if (s->first_line == NULL)
508 for (e = s->macros; e; e = e->next)
509 if (!strcasecmp (e->id, macro_name))
510 return e->replacement[s->loop_index];
516 /* Makes appropriate DO REPEAT macro substitutions within getl_buf. */
518 perform_DO_REPEAT_substitutions (void)
520 /* Are we in an apostrophized string or a quoted string? */
521 int in_apos = 0, in_quote = 0;
523 /* Source pointer. */
526 /* Output buffer, size, pointer. */
527 struct string output;
532 ds_init (&output, ds_capacity (&getl_buf));
534 /* Strip trailing whitespace, check for & remove terminal dot. */
535 while (ds_length (&getl_buf) > 0
536 && isspace ((unsigned char) ds_end (&getl_buf)[-1]))
537 ds_truncate (&getl_buf, ds_length (&getl_buf) - 1);
538 if (ds_length (&getl_buf) > 0 && ds_end (&getl_buf)[-1] == get_endcmd() )
541 ds_truncate (&getl_buf, ds_length (&getl_buf) - 1);
544 for (cp = ds_c_str (&getl_buf); cp < ds_end (&getl_buf); )
546 if (*cp == '\'' && !in_quote)
548 else if (*cp == '"' && !in_apos)
551 if (in_quote || in_apos || !CHAR_IS_ID1 (*cp))
553 ds_putc (&output, *cp++);
557 /* Collect an identifier. */
559 char name[LONG_NAME_LEN + 1];
564 while (CHAR_IS_IDN (*cp) && np < &name[LONG_NAME_LEN])
566 while (CHAR_IS_IDN (*cp))
570 substitution = find_DO_REPEAT_substitution (name);
573 ds_concat (&output, start, cp - start);
577 /* Force output buffer size, copy substitution. */
578 ds_puts (&output, substitution);
582 ds_putc (&output, get_endcmd ());
584 ds_destroy (&getl_buf);