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