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"
37 #include "debug-print.h"
39 /* Describes one DO REPEAT macro. */
42 int type; /* 1=variable names, 0=any other. */
43 char id[LONG_NAME_LEN + 1]; /* Macro identifier. */
44 char **replacement; /* Macro replacement. */
45 struct repeat_entry *next;
48 /* List of macro identifiers. */
49 static struct repeat_entry *repeat_tab;
51 /* Number of substitutions for each macro. */
54 /* List of lines before it's actually assigned to a file. */
55 static struct getl_line_list *line_buf_head;
56 static struct getl_line_list *line_buf_tail;
58 static int parse_ids (struct repeat_entry *);
59 static int parse_numbers (struct repeat_entry *);
60 static int parse_strings (struct repeat_entry *);
61 static void clean_up (void);
62 static int internal_cmd_do_repeat (void);
67 if (internal_cmd_do_repeat ())
74 /* Garbage collects all the allocated memory that's no longer
79 struct repeat_entry *iter, *next;
87 if (iter->replacement)
89 for (i = 0; i < count; i++)
90 free (iter->replacement[i]);
91 free (iter->replacement);
99 /* Allocates & appends another record at the end of the line_buf_tail
104 struct getl_line_list *new = xmalloc (sizeof *new);
106 if (line_buf_head == NULL)
107 line_buf_head = line_buf_tail = new;
109 line_buf_tail = line_buf_tail->next = new;
112 /* Returns nonzero if KEYWORD appears beginning at CONTEXT. */
114 recognize_keyword (const char *context, const char *keyword)
116 const char *end = context;
117 while (isalpha ((unsigned char) *end))
119 return lex_id_match_len (keyword, strlen (keyword), context, end - context);
122 /* Does the real work of parsing the DO REPEAT command and its nested
125 internal_cmd_do_repeat (void)
127 /* Name of first DO REPEAT macro. */
128 char first_name[LONG_NAME_LEN + 1];
130 /* Current filename. */
131 const char *current_filename = NULL;
133 /* 1=Print lines after preprocessing. */
136 /* The first step is parsing the DO REPEAT command itself. */
138 line_buf_head = NULL;
141 struct repeat_entry *e;
142 struct repeat_entry *iter;
145 /* Get a stand-in variable name and make sure it's unique. */
146 if (!lex_force_id ())
148 for (iter = repeat_tab; iter; iter = iter->next)
149 if (!strcmp (iter->id, tokid))
151 msg (SE, _("Identifier %s is given twice."), tokid);
155 /* Make a new stand-in variable entry and link it into the
157 e = xmalloc (sizeof *e);
159 e->next = repeat_tab;
160 strcpy (e->id, tokid);
163 /* Skip equals sign. */
165 if (!lex_force_match ('='))
168 /* Get the details of the variable's possible values. */
171 result = parse_ids (e);
172 else if (lex_is_number ())
173 result = parse_numbers (e);
174 else if (token == T_STRING)
175 result = parse_strings (e);
184 /* If this is the first variable then it defines how many
185 replacements there must be; otherwise enforce this number of
190 strcpy (first_name, e->id);
192 else if (count != result)
194 msg (SE, _("There must be the same number of substitutions "
195 "for each dummy variable specified. Since there "
196 "were %d substitutions for %s, there must be %d "
197 "for %s as well, but %d were specified."),
198 count, first_name, count, e->id, result);
205 while (token != '.');
207 /* Read all the lines inside the DO REPEAT ... END REPEAT. */
213 if (!getl_read_line ())
214 msg (FE, _("Unexpected end of file."));
216 /* If the current file has changed then record the fact. */
221 getl_location (&curfn, &curln);
222 if (current_filename != curfn)
224 assert (curln > 0 && curfn != NULL);
227 line_buf_tail->len = -curln;
228 line_buf_tail->line = xstrdup (curfn);
229 current_filename = curfn;
233 /* FIXME? This code is not strictly correct, however if you
234 have begun a line with DO REPEAT or END REPEAT and it's
235 *not* a command name, then you are obviously *trying* to
236 break this mechanism. And you will. Also, the entire
237 command names must appear on a single line--they can't be
240 char *cp = ds_c_str (&getl_buf);
242 /* Skip leading indentors and any whitespace. */
243 if (*cp == '+' || *cp == '-' || *cp == '.')
245 while (isspace ((unsigned char) *cp))
248 /* Find END REPEAT. */
249 if (recognize_keyword (cp, "end"))
251 while (isalpha ((unsigned char) *cp))
253 while (isspace ((unsigned char) *cp))
255 if (recognize_keyword (cp, "repeat"))
261 while (isalpha ((unsigned char) *cp))
263 while (isspace ((unsigned char) *cp))
266 print = recognize_keyword (cp, "print");
271 else /* Find DO REPEAT. */
272 if (!strncasecmp (cp, "do", 2))
275 while (isspace ((unsigned char) *cp))
277 if (!strncasecmp (cp, "rep", 3))
283 line_buf_tail->len = ds_length (&getl_buf);
284 line_buf_tail->line = xmalloc (ds_length (&getl_buf) + 1);
285 memcpy (line_buf_tail->line,
286 ds_c_str (&getl_buf), ds_length (&getl_buf) + 1);
290 /* FIXME: For the moment we simply discard the contents of the END
291 REPEAT line. We should actually check for the PRINT specifier.
292 This can be done easier when we buffer entire commands instead of
293 doing it token by token; see TODO. */
296 /* Tie up the loose end of the chain. */
297 if (line_buf_head == NULL)
299 msg (SW, _("No commands in scope."));
302 line_buf_tail->next = NULL;
304 /* Make new variables. */
306 struct repeat_entry *iter;
307 for (iter = repeat_tab; iter; iter = iter->next)
311 for (i = 0; i < count; i++)
313 /* Note that if the variable already exists there is no
315 dict_create_var (default_dict, iter->replacement[i], 0);
320 /* Create the DO REPEAT virtual input file. */
322 struct getl_script *script = xmalloc (sizeof *script);
324 script->first_line = line_buf_head;
325 script->cur_line = NULL;
326 script->remaining_loops = count;
327 script->loop_index = -1;
328 script->macros = repeat_tab;
329 script->print = print;
331 getl_add_DO_REPEAT_file (script);
337 /* Parses a set of ids for DO REPEAT. */
339 parse_ids (struct repeat_entry * e)
345 e->replacement = NULL;
352 if (!parse_mixed_vars (&names, &nnames, PV_NONE))
355 e->replacement = xrealloc (e->replacement,
356 (nnames + n) * sizeof *e->replacement);
357 for (i = 0; i < nnames; i++)
359 e->replacement[n + i] = xstrdup (names[i]);
365 while (token != '/' && token != '.');
370 /* Stores VALUE into *REPL. */
372 store_numeric (char **repl, long value)
374 *repl = xmalloc (INT_DIGITS + 1);
375 sprintf (*repl, "%ld", value);
378 /* Parses a list of numbers for DO REPEAT. */
380 parse_numbers (struct repeat_entry *e)
382 /* First and last numbers for TO, plus the step factor. */
385 /* Alias to e->replacement. */
388 /* Number of entries in array; maximum number for this allocation
394 e->replacement = array = NULL;
398 /* Parse A TO B into a, b. */
399 if (!lex_force_int ())
407 if (!lex_force_int ())
415 if (n + (abs (b - a) + 1) > m)
417 m = n + (abs (b - a) + 1) + 16;
418 e->replacement = array = xrealloc (array,
419 m * sizeof *e->replacement);
423 store_numeric (&array[n++], a);
429 for (iter = a; iter <= b; iter++)
430 store_numeric (&array[n++], iter);
432 for (iter = a; iter >= b; iter--)
433 store_numeric (&array[n++], iter);
438 while (token != '/' && token != '.');
439 e->replacement = xrealloc (array, n * sizeof *e->replacement);
444 /* Parses a list of strings for DO REPEAT. */
446 parse_strings (struct repeat_entry * e)
452 string = e->replacement = NULL;
457 if (token != T_STRING)
460 msg (SE, _("String expected."));
461 for (i = 0; i < n; i++)
470 e->replacement = string = xrealloc (string,
471 m * sizeof *e->replacement);
473 string[n++] = lex_token_representation ();
478 while (token != '/' && token != '.');
479 e->replacement = xrealloc (string, n * sizeof *e->replacement);
485 cmd_end_repeat (void)
487 msg (SE, _("No matching DO REPEAT."));
491 /* Finds a DO REPEAT macro with name MACRO_NAME and returns the
492 appropriate subsitution if found, or NULL if not. */
494 find_DO_REPEAT_substitution (char *macro_name)
496 struct getl_script *s;
498 for (s = getl_head; s; s = s->included_from)
500 struct repeat_entry *e;
502 if (s->first_line == NULL)
505 for (e = s->macros; e; e = e->next)
506 if (!strcasecmp (e->id, macro_name))
507 return e->replacement[s->loop_index];
513 /* Makes appropriate DO REPEAT macro substitutions within getl_buf. */
515 perform_DO_REPEAT_substitutions (void)
517 /* Are we in an apostrophized string or a quoted string? */
518 int in_apos = 0, in_quote = 0;
520 /* Source pointer. */
523 /* Output buffer, size, pointer. */
524 struct string output;
529 ds_init (&output, ds_capacity (&getl_buf));
531 /* Strip trailing whitespace, check for & remove terminal dot. */
532 while (ds_length (&getl_buf) > 0
533 && isspace ((unsigned char) ds_end (&getl_buf)[-1]))
534 ds_truncate (&getl_buf, ds_length (&getl_buf) - 1);
535 if (ds_length (&getl_buf) > 0 && ds_end (&getl_buf)[-1] == get_endcmd() )
538 ds_truncate (&getl_buf, ds_length (&getl_buf) - 1);
541 for (cp = ds_c_str (&getl_buf); cp < ds_end (&getl_buf); )
543 if (*cp == '\'' && !in_quote)
545 else if (*cp == '"' && !in_apos)
548 if (in_quote || in_apos || !CHAR_IS_ID1 (*cp))
550 ds_putc (&output, *cp++);
554 /* Collect an identifier. */
556 char name[LONG_NAME_LEN + 1];
561 while (CHAR_IS_IDN (*cp) && np < &name[LONG_NAME_LEN])
563 while (CHAR_IS_IDN (*cp))
567 substitution = find_DO_REPEAT_substitution (name);
570 ds_concat (&output, start, cp - start);
574 /* Force output buffer size, copy substitution. */
575 ds_puts (&output, substitution);
579 ds_putc (&output, get_endcmd() );
581 ds_destroy (&getl_buf);