bf89fedecd50f0b5e131ed3c2141d814e59fa5f1
[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 "repeat.h"
22 #include <assert.h>
23 #include <ctype.h>
24 #include <math.h>
25 #include <stdlib.h>
26 #include "alloc.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               dict_create_var (default_dict, iter->replacement[i], 0);
332             }
333         }
334   }
335
336   /* Create the DO REPEAT virtual input file. */
337   {
338     struct getl_script *script = xmalloc (sizeof *script);
339
340     script->first_line = line_buf_head;
341     script->cur_line = NULL;
342     script->remaining_loops = count;
343     script->loop_index = -1;
344     script->macros = repeat_tab;
345     script->print = print;
346
347     getl_add_DO_REPEAT_file (script);
348   }
349
350   return 1;
351 }
352
353 /* Parses a set of ids for DO REPEAT. */
354 static int
355 parse_ids (struct repeat_entry * e)
356 {
357   int i;
358   int n = 0;
359
360   e->type = 1;
361   e->replacement = NULL;
362
363   do
364     {
365       char **names;
366       int nnames;
367
368       if (!parse_mixed_vars (&names, &nnames, PV_NONE))
369         return 0;
370
371       e->replacement = xrealloc (e->replacement,
372                                  (nnames + n) * sizeof *e->replacement);
373       for (i = 0; i < nnames; i++)
374         {
375           e->replacement[n + i] = xstrdup (names[i]);
376           free (names[i]);
377         }
378       free (names);
379       n += nnames;
380     }
381   while (token != '/' && token != '.');
382
383   return n;
384 }
385
386 /* Stores VALUE into *REPL. */
387 static inline void
388 store_numeric (char **repl, long value)
389 {
390   *repl = xmalloc (INT_DIGITS + 1);
391   sprintf (*repl, "%ld", value);
392 }
393
394 /* Parses a list of numbers for DO REPEAT. */
395 static int
396 parse_numbers (struct repeat_entry *e)
397 {
398   /* First and last numbers for TO, plus the step factor. */
399   long a, b;
400
401   /* Alias to e->replacement. */
402   char **array;
403
404   /* Number of entries in array; maximum number for this allocation
405      size. */
406   int n, m;
407
408   n = m = 0;
409   e->type = 0;
410   e->replacement = array = NULL;
411
412   do
413     {
414       /* Parse A TO B into a, b. */
415       if (!lex_force_int ())
416         return 0;
417       a = lex_integer ();
418
419       lex_get ();
420       if (token == T_TO)
421         {
422           lex_get ();
423           if (!lex_force_int ())
424             return 0;
425           b = lex_integer ();
426
427           lex_get ();
428         }
429       else b = a;
430
431       if (n + (abs (b - a) + 1) > m)
432         {
433           m = n + (abs (b - a) + 1) + 16;
434           e->replacement = array = xrealloc (array,
435                                              m * sizeof *e->replacement);
436         }
437
438       if (a == b)
439         store_numeric (&array[n++], a);
440       else
441         {
442           long iter;
443
444           if (a < b)
445             for (iter = a; iter <= b; iter++)
446               store_numeric (&array[n++], iter);
447           else
448             for (iter = a; iter >= b; iter--)
449               store_numeric (&array[n++], iter);
450         }
451
452       lex_match (',');
453     }
454   while (token != '/' && token != '.');
455   e->replacement = xrealloc (array, n * sizeof *e->replacement);
456
457   return n;
458 }
459
460 /* Parses a list of strings for DO REPEAT. */
461 int
462 parse_strings (struct repeat_entry * e)
463 {
464   char **string;
465   int n, m;
466
467   e->type = 0;
468   string = e->replacement = NULL;
469   n = m = 0;
470
471   do
472     {
473       if (token != T_STRING)
474         {
475           int i;
476           msg (SE, _("String expected."));
477           for (i = 0; i < n; i++)
478             free (string[i]);
479           free (string);
480           return 0;
481         }
482
483       if (n + 1 > m)
484         {
485           m += 16;
486           e->replacement = string = xrealloc (string,
487                                               m * sizeof *e->replacement);
488         }
489       string[n++] = lex_token_representation ();
490       lex_get ();
491
492       lex_match (',');
493     }
494   while (token != '/' && token != '.');
495   e->replacement = xrealloc (string, n * sizeof *e->replacement);
496
497   return n;
498 }
499 \f
500 int
501 cmd_end_repeat (void)
502 {
503   msg (SE, _("No matching DO REPEAT."));
504   return CMD_FAILURE;
505 }
506 \f
507 /* Finds a DO REPEAT macro with name MACRO_NAME and returns the
508    appropriate subsitution if found, or NULL if not. */
509 static char *
510 find_DO_REPEAT_substitution (char *macro_name)
511 {
512   struct getl_script *s;
513             
514   for (s = getl_head; s; s = s->included_from)
515     {
516       struct repeat_entry *e;
517       
518       if (s->first_line == NULL)
519         continue;
520
521       for (e = s->macros; e; e = e->next)
522         if (!strcasecmp (e->id, macro_name))
523           return e->replacement[s->loop_index];
524     }
525   
526   return NULL;
527 }
528
529 /* Makes appropriate DO REPEAT macro substitutions within getl_buf. */
530 void
531 perform_DO_REPEAT_substitutions (void)
532 {
533   /* Are we in an apostrophized string or a quoted string? */
534   int in_apos = 0, in_quote = 0;
535
536   /* Source pointer. */
537   char *cp;
538
539   /* Output buffer, size, pointer. */
540   struct string output;
541
542   /* Terminal dot. */
543   int dot = 0;
544
545   ds_init (NULL, &output, ds_size (&getl_buf));
546
547   /* Strip trailing whitespace, check for & remove terminal dot. */
548   while (ds_length (&getl_buf) > 0
549          && isspace ((unsigned char) ds_end (&getl_buf)[-1]))
550     ds_truncate (&getl_buf, ds_length (&getl_buf) - 1);
551   if (ds_length (&getl_buf) > 0 && ds_end (&getl_buf)[-1] == set_endcmd)
552     {
553       dot = 1;
554       ds_truncate (&getl_buf, ds_length (&getl_buf) - 1);
555     }
556   
557   for (cp = ds_value (&getl_buf); cp < ds_end (&getl_buf); )
558     {
559       if (*cp == '\'' && !in_quote)
560         in_apos ^= 1;
561       else if (*cp == '"' && !in_apos)
562         in_quote ^= 1;
563       
564       if (in_quote || in_apos || !CHAR_IS_ID1 (*cp))
565         {
566           ds_putchar (&output, *cp++);
567           continue;
568         }
569
570       /* Collect an identifier. */
571       {
572         char name[9];
573         char *start = cp;
574         char *np = name;
575         char *substitution;
576
577         while (CHAR_IS_IDN (*cp) && np < &name[8])
578           *np++ = *cp++;
579         while (CHAR_IS_IDN (*cp))
580           cp++;
581         *np = 0;
582
583         substitution = find_DO_REPEAT_substitution (name);
584         if (!substitution)
585           {
586             ds_concat_buffer (&output, start, cp - start);
587             continue;
588           }
589
590         /* Force output buffer size, copy substitution. */
591         ds_concat (&output, substitution);
592       }
593     }
594   if (dot)
595     ds_putchar (&output, (unsigned char) set_endcmd);
596
597   ds_destroy (&getl_buf);
598   getl_buf = output;
599 }
600 \f
601 /* Debugging code. */
602
603 #if DEBUGGING
604 static void
605 debug_print (void)
606 {
607   struct repeat_entry *iter;
608   int j;
609
610   printf ("DO REPEAT\n");
611   for (iter = repeat_tab; iter; iter = iter->next)
612     {
613       printf ("   %s%s=", iter->id, iter->type ? "(ids)" : "");
614       for (j = 0; j < count; j++)
615         printf ("%s ", iter->replacement[j]);
616       putc (iter->next ? '/' : '.', stdout);
617       printf ("\n");
618     }
619 }
620
621 static void
622 debug_print_lines (void)
623 {
624   struct getl_line_list *iter;
625   const char *fn = "(none)";
626   int ln = 65536;
627
628   printf ("---begin DO REPEAT lines---\n");
629   for (iter = line_buf_head; iter; iter = iter->next)
630     {
631       if (iter->len < 0)
632         {
633           ln = -iter->len;
634           fn = iter->line;
635         } else {
636           printf ("%s:%d: %s", fn, ln++, iter->line);
637         }
638     }
639   printf ("---end DO REPEAT lines---\n");
640 }
641 #endif /* DEBUGGING */