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