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