Sat Dec 27 16:16:49 2003 Ben Pfaff <blp@gnu.org>
[pspp] / src / repeat.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 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 "repeat.h"
22 #include <assert.h>
23 #include <ctype.h>
24 #include <math.h>
25 #include <stdlib.h>
26 #include "alloc.h"
27 #include "cases.h"
28 #include "command.h"
29 #include "error.h"
30 #include "getline.h"
31 #include "lexer.h"
32 #include "misc.h"
33 #include "settings.h"
34 #include "str.h"
35 #include "var.h"
36
37 #include "debug-print.h"
38
39 /* Describes one DO REPEAT macro. */
40 struct repeat_entry
41   {
42     int type;                   /* 1=variable names, 0=any other. */
43     char id[9];                 /* Macro identifier. */
44     char **replacement;         /* Macro replacement. */
45     struct repeat_entry *next;
46   };
47
48 /* List of macro identifiers. */
49 static struct repeat_entry *repeat_tab;
50
51 /* Number of substitutions for each macro. */
52 static int count;
53
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;
57
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);
63
64 #if DEBUGGING
65 static void debug_print (void);
66 static void debug_print_lines (void);
67 #endif
68
69 int
70 cmd_do_repeat (void)
71 {
72   if (internal_cmd_do_repeat ())
73     return CMD_SUCCESS;
74
75   clean_up ();
76   return CMD_FAILURE;
77 }
78
79 /* Garbage collects all the allocated memory that's no longer
80    needed. */
81 static void
82 clean_up (void)
83 {
84   struct repeat_entry *iter, *next;
85   int i;
86
87   iter = repeat_tab;
88   repeat_tab = NULL;
89
90   while (iter)
91     {
92       if (iter->replacement)
93         {
94           for (i = 0; i < count; i++)
95             free (iter->replacement[i]);
96           free (iter->replacement);
97         }
98       next = iter->next;
99       free (iter);
100       iter = next;
101     }
102 }
103
104 /* Allocates & appends another record at the end of the line_buf_tail
105    chain. */
106 static inline void
107 append_record (void)
108 {
109   struct getl_line_list *new = xmalloc (sizeof *new);
110   
111   if (line_buf_head == NULL)
112     line_buf_head = line_buf_tail = new;
113   else
114     line_buf_tail = line_buf_tail->next = new;
115 }
116
117 /* Returns nonzero if KEYWORD appears beginning at CONTEXT. */
118 static int
119 recognize_keyword (const char *context, const char *keyword)
120 {
121   const char *end = context;
122   while (isalpha ((unsigned char) *end))
123     end++;
124   return lex_id_match_len (keyword, strlen (keyword), context, end - context);
125 }
126
127 /* Does the real work of parsing the DO REPEAT command and its nested
128    commands. */
129 static int
130 internal_cmd_do_repeat (void)
131 {
132   /* Name of first DO REPEAT macro. */
133   char first_name[9];
134
135   /* Current filename. */
136   const char *current_filename = NULL;
137
138   /* 1=Print lines after preprocessing. */
139   int print;
140
141   /* The first step is parsing the DO REPEAT command itself. */
142   lex_match_id ("DO");
143   lex_match_id ("REPEAT");
144
145   count = 0;
146   line_buf_head = NULL;
147   do
148     {
149       struct repeat_entry *e;
150       struct repeat_entry *iter;
151       int result;
152
153       /* Get a stand-in variable name and make sure it's unique. */
154       if (!lex_force_id ())
155         return 0;
156       for (iter = repeat_tab; iter; iter = iter->next)
157         if (!strcmp (iter->id, tokid))
158           {
159             msg (SE, _("Identifier %s is given twice."), tokid);
160             return 0;
161           }
162
163       /* Make a new stand-in variable entry and link it into the
164          list. */
165       e = xmalloc (sizeof *e);
166       e->type = 0;
167       e->next = repeat_tab;
168       strcpy (e->id, tokid);
169       repeat_tab = e;
170
171       /* Skip equals sign. */
172       lex_get ();
173       if (!lex_force_match ('='))
174         return 0;
175
176       /* Get the details of the variable's possible values. */
177       
178       if (token == T_ID)
179         result = parse_ids (e);
180       else if (token == T_NUM)
181         result = parse_numbers (e);
182       else if (token == T_STRING)
183         result = parse_strings (e);
184       else
185         {
186           lex_error (NULL);
187           return 0;
188         }
189       if (!result)
190         return 0;
191
192       /* If this is the first variable then it defines how many
193          replacements there must be; otherwise enforce this number of
194          replacements. */
195       if (!count)
196         {
197           count = result;
198           strcpy (first_name, e->id);
199         }
200       else if (count != result)
201         {
202           msg (SE, _("There must be the same number of substitutions "
203                      "for each dummy variable specified.  Since there "
204                      "were %d substitutions for %s, there must be %d "
205                      "for %s as well, but %d were specified."),
206                count, first_name, count, e->id, result);
207           return 0;
208         }
209
210       /* Next! */
211       lex_match ('/');
212     }
213   while (token != '.');
214
215 #if DEBUGGING
216   debug_print ();
217 #endif
218
219   /* Read all the lines inside the DO REPEAT ... END REPEAT. */
220   {
221     int nest = 1;
222
223     for (;;)
224       {
225         if (!getl_read_line ())
226           msg (FE, _("Unexpected end of file."));
227
228         /* If the current file has changed then record the fact. */
229         {
230           const char *curfn;
231           int curln;
232
233           getl_location (&curfn, &curln);
234           if (current_filename != curfn)
235             {
236               assert (curln > 0 && curfn != NULL);
237             
238               append_record ();
239               line_buf_tail->len = -curln;
240               line_buf_tail->line = xstrdup (curfn);
241               current_filename = curfn;
242             }
243         }
244         
245         /* FIXME?  This code is not strictly correct, however if you
246            have begun a line with DO REPEAT or END REPEAT and it's
247            *not* a command name, then you are obviously *trying* to
248            break this mechanism.  And you will.  Also, the entire
249            command names must appear on a single line--they can't be
250            spread out. */
251         {
252           char *cp = ds_value (&getl_buf);
253
254           /* Skip leading indentors and any whitespace. */
255           if (*cp == '+' || *cp == '-' || *cp == '.')
256             cp++;
257           while (isspace ((unsigned char) *cp))
258             cp++;
259
260           /* Find END REPEAT. */
261           if (recognize_keyword (cp, "end"))
262             {
263               while (isalpha ((unsigned char) *cp))
264                 cp++;
265               while (isspace ((unsigned char) *cp))
266                 cp++;
267               if (recognize_keyword (cp, "repeat"))
268                 {
269                   nest--;
270
271                   if (!nest)
272                   {
273                     while (isalpha ((unsigned char) *cp))
274                       cp++;
275                     while (isspace ((unsigned char) *cp))
276                       cp++;
277
278                     print = recognize_keyword (cp, "print");
279                     break;
280                   }
281                 }
282             }
283           else /* Find DO REPEAT. */
284             if (!strncasecmp (cp, "do", 2))
285               {
286                 cp += 2;
287                 while (isspace ((unsigned char) *cp))
288                   cp++;
289                 if (!strncasecmp (cp, "rep", 3))
290                   nest++;
291               }
292         }
293
294         append_record ();
295         line_buf_tail->len = ds_length (&getl_buf);
296         line_buf_tail->line = xmalloc (ds_length (&getl_buf) + 1);
297         memcpy (line_buf_tail->line,
298                 ds_value (&getl_buf), ds_length (&getl_buf) + 1);
299       }
300   }
301
302   /* FIXME: For the moment we simply discard the contents of the END
303      REPEAT line.  We should actually check for the PRINT specifier.
304      This can be done easier when we buffer entire commands instead of
305      doing it token by token; see TODO. */
306   lex_entire_line ();   
307   
308   /* Tie up the loose end of the chain. */
309   if (line_buf_head == NULL)
310     {
311       msg (SW, _("No commands in scope."));
312       return 1;
313     }
314   line_buf_tail->next = NULL;
315
316   /* Show the line list. */
317 #if DEBUGGING
318   debug_print_lines ();
319 #endif
320   
321   /* Make new variables. */
322   {
323     struct repeat_entry *iter;
324     for (iter = repeat_tab; iter; iter = iter->next)
325       if (iter->type == 1)
326         {
327           int i;
328           for (i = 0; i < count; i++)
329             {
330               /* Note that if the variable already exists there is no
331                  harm done. */
332               struct variable *v = dict_create_var (default_dict,
333                                                     iter->replacement[i],
334                                                     0);
335
336               /* If we created the variable then we need to initialize
337                  its observations to SYSMIS. */
338               if (v)
339                 envector (v);
340             }
341         }
342   }
343
344   /* Create the DO REPEAT virtual input file. */
345   {
346     struct getl_script *script = xmalloc (sizeof *script);
347
348     script->first_line = line_buf_head;
349     script->cur_line = NULL;
350     script->remaining_loops = count;
351     script->loop_index = -1;
352     script->macros = repeat_tab;
353     script->print = print;
354
355     getl_add_DO_REPEAT_file (script);
356   }
357
358   return 1;
359 }
360
361 /* Parses a set of ids for DO REPEAT. */
362 static int
363 parse_ids (struct repeat_entry * e)
364 {
365   int i;
366   int n = 0;
367
368   e->type = 1;
369   e->replacement = NULL;
370
371   do
372     {
373       char **names;
374       int nnames;
375
376       if (!parse_mixed_vars (&names, &nnames, PV_NONE))
377         return 0;
378
379       e->replacement = xrealloc (e->replacement,
380                                  (nnames + n) * sizeof *e->replacement);
381       for (i = 0; i < nnames; i++)
382         {
383           e->replacement[n + i] = xstrdup (names[i]);
384           free (names[i]);
385         }
386       free (names);
387       n += nnames;
388     }
389   while (token != '/' && token != '.');
390
391   return n;
392 }
393
394 /* Stores VALUE into *REPL. */
395 static inline void
396 store_numeric (char **repl, long value)
397 {
398   *repl = xmalloc (INT_DIGITS + 1);
399   sprintf (*repl, "%ld", value);
400 }
401
402 /* Parses a list of numbers for DO REPEAT. */
403 static int
404 parse_numbers (struct repeat_entry *e)
405 {
406   /* First and last numbers for TO, plus the step factor. */
407   long a, b;
408
409   /* Alias to e->replacement. */
410   char **array;
411
412   /* Number of entries in array; maximum number for this allocation
413      size. */
414   int n, m;
415
416   n = m = 0;
417   e->type = 0;
418   e->replacement = array = NULL;
419
420   do
421     {
422       /* Parse A TO B into a, b. */
423       if (!lex_force_int ())
424         return 0;
425       a = lex_integer ();
426
427       lex_get ();
428       if (token == T_TO)
429         {
430           lex_get ();
431           if (!lex_force_int ())
432             return 0;
433           b = lex_integer ();
434
435           lex_get ();
436         }
437       else b = a;
438
439       if (n + (abs (b - a) + 1) > m)
440         {
441           m = n + (abs (b - a) + 1) + 16;
442           e->replacement = array = xrealloc (array,
443                                              m * sizeof *e->replacement);
444         }
445
446       if (a == b)
447         store_numeric (&array[n++], a);
448       else
449         {
450           long iter;
451
452           if (a < b)
453             for (iter = a; iter <= b; iter++)
454               store_numeric (&array[n++], iter);
455           else
456             for (iter = a; iter >= b; iter--)
457               store_numeric (&array[n++], iter);
458         }
459
460       lex_match (',');
461     }
462   while (token != '/' && token != '.');
463   e->replacement = xrealloc (array, n * sizeof *e->replacement);
464
465   return n;
466 }
467
468 /* Parses a list of strings for DO REPEAT. */
469 int
470 parse_strings (struct repeat_entry * e)
471 {
472   char **string;
473   int n, m;
474
475   e->type = 0;
476   string = e->replacement = NULL;
477   n = m = 0;
478
479   do
480     {
481       if (token != T_STRING)
482         {
483           int i;
484           msg (SE, _("String expected."));
485           for (i = 0; i < n; i++)
486             free (string[i]);
487           free (string);
488           return 0;
489         }
490
491       if (n + 1 > m)
492         {
493           m += 16;
494           e->replacement = string = xrealloc (string,
495                                               m * sizeof *e->replacement);
496         }
497       string[n++] = lex_token_representation ();
498       lex_get ();
499
500       lex_match (',');
501     }
502   while (token != '/' && token != '.');
503   e->replacement = xrealloc (string, n * sizeof *e->replacement);
504
505   return n;
506 }
507 \f
508 int
509 cmd_end_repeat (void)
510 {
511   msg (SE, _("No matching DO REPEAT."));
512   return CMD_FAILURE;
513 }
514 \f
515 /* Finds a DO REPEAT macro with name MACRO_NAME and returns the
516    appropriate subsitution if found, or NULL if not. */
517 static char *
518 find_DO_REPEAT_substitution (char *macro_name)
519 {
520   struct getl_script *s;
521             
522   for (s = getl_head; s; s = s->included_from)
523     {
524       struct repeat_entry *e;
525       
526       if (s->first_line == NULL)
527         continue;
528
529       for (e = s->macros; e; e = e->next)
530         if (!strcasecmp (e->id, macro_name))
531           return e->replacement[s->loop_index];
532     }
533   
534   return NULL;
535 }
536
537 /* Makes appropriate DO REPEAT macro substitutions within getl_buf. */
538 void
539 perform_DO_REPEAT_substitutions (void)
540 {
541   /* Are we in an apostrophized string or a quoted string? */
542   int in_apos = 0, in_quote = 0;
543
544   /* Source pointer. */
545   char *cp;
546
547   /* Output buffer, size, pointer. */
548   struct string output;
549
550   /* Terminal dot. */
551   int dot = 0;
552
553   ds_init (NULL, &output, ds_size (&getl_buf));
554
555   /* Strip trailing whitespace, check for & remove terminal dot. */
556   while (ds_length (&getl_buf) > 0
557          && isspace ((unsigned char) ds_end (&getl_buf)[-1]))
558     ds_truncate (&getl_buf, ds_length (&getl_buf) - 1);
559   if (ds_length (&getl_buf) > 0 && ds_end (&getl_buf)[-1] == set_endcmd)
560     {
561       dot = 1;
562       ds_truncate (&getl_buf, ds_length (&getl_buf) - 1);
563     }
564   
565   for (cp = ds_value (&getl_buf); cp < ds_end (&getl_buf); )
566     {
567       if (*cp == '\'' && !in_quote)
568         in_apos ^= 1;
569       else if (*cp == '"' && !in_apos)
570         in_quote ^= 1;
571       
572       if (in_quote || in_apos || !CHAR_IS_ID1 (*cp))
573         {
574           ds_putchar (&output, *cp++);
575           continue;
576         }
577
578       /* Collect an identifier. */
579       {
580         char name[9];
581         char *start = cp;
582         char *np = name;
583         char *substitution;
584
585         while (CHAR_IS_IDN (*cp) && np < &name[8])
586           *np++ = *cp++;
587         while (CHAR_IS_IDN (*cp))
588           cp++;
589         *np = 0;
590
591         substitution = find_DO_REPEAT_substitution (name);
592         if (!substitution)
593           {
594             ds_concat_buffer (&output, start, cp - start);
595             continue;
596           }
597
598         /* Force output buffer size, copy substitution. */
599         ds_concat (&output, substitution);
600       }
601     }
602   if (dot)
603     ds_putchar (&output, (unsigned char) set_endcmd);
604
605   ds_destroy (&getl_buf);
606   getl_buf = output;
607 }
608 \f
609 /* Debugging code. */
610
611 #if DEBUGGING
612 static void
613 debug_print (void)
614 {
615   struct repeat_entry *iter;
616   int j;
617
618   printf ("DO REPEAT\n");
619   for (iter = repeat_tab; iter; iter = iter->next)
620     {
621       printf ("   %s%s=", iter->id, iter->type ? "(ids)" : "");
622       for (j = 0; j < count; j++)
623         printf ("%s ", iter->replacement[j]);
624       putc (iter->next ? '/' : '.', stdout);
625       printf ("\n");
626     }
627 }
628
629 static void
630 debug_print_lines (void)
631 {
632   struct getl_line_list *iter;
633   const char *fn = "(none)";
634   int ln = 65536;
635
636   printf ("---begin DO REPEAT lines---\n");
637   for (iter = line_buf_head; iter; iter = iter->next)
638     {
639       if (iter->len < 0)
640         {
641           ln = -iter->len;
642           fn = iter->line;
643         } else {
644           printf ("%s:%d: %s", fn, ln++, iter->line);
645         }
646     }
647   printf ("---end DO REPEAT lines---\n");
648 }
649 #endif /* DEBUGGING */