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