384ac775faf49e4185eec0b72c1a0430e0faf07c
[pspp-builds.git] / src / language / control / 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
22 #include "repeat.h"
23
24 #include <ctype.h>
25 #include <math.h>
26 #include <stdlib.h>
27
28 #include <data/dictionary.h>
29 #include <data/procedure.h>
30 #include <data/settings.h>
31 #include <language/command.h>
32 #include <language/lexer/lexer.h>
33 #include <language/lexer/variable-parser.h>
34 #include <language/line-buffer.h>
35 #include <libpspp/alloc.h>
36 #include <libpspp/message.h>
37 #include <libpspp/message.h>
38 #include <libpspp/misc.h>
39 #include <libpspp/pool.h>
40 #include <libpspp/str.h>
41 #include <data/variable.h>
42
43 #include "intprops.h"
44
45 #include "gettext.h"
46 #define _(msgid) gettext (msgid)
47
48 /* Defines a list of lines used by DO REPEAT. */
49 struct line_list
50   {
51     struct line_list *next;     /* Next line. */
52     char *file_name;            /* File name. */
53     int line_number;            /* Line number. */
54     char *line;                 /* Contents. */
55   };
56
57 /* The type of substitution made for a DO REPEAT macro. */
58 enum repeat_entry_type 
59   {
60     VAR_NAMES,
61     OTHER
62   };
63
64 /* Describes one DO REPEAT macro. */
65 struct repeat_entry
66   {
67     struct repeat_entry *next;          /* Next entry. */
68     enum repeat_entry_type type;        /* Types of replacements. */
69     char id[LONG_NAME_LEN + 1];         /* Macro identifier. */
70     char **replacement;                 /* Macro replacement. */
71   };
72
73 /* A DO REPEAT...END REPEAT block. */
74 struct repeat_block 
75   {
76     struct pool *pool;                  /* Pool used for storage. */
77     struct dataset *ds;                 /* The dataset for this block */
78     struct line_list *first_line;       /* First line in line buffer. */
79     struct line_list *cur_line;         /* Current line in line buffer. */
80     int loop_cnt;                       /* Number of loops. */
81     int loop_idx;                       /* Number of loops so far. */
82     struct repeat_entry *macros;        /* Pointer to macro table. */
83     bool print;                         /* Print lines as executed? */
84   };
85
86 static bool parse_specification (struct lexer *, struct repeat_block *);
87 static bool parse_lines (struct lexer *, struct repeat_block *);
88 static void create_vars (struct repeat_block *);
89
90 static int parse_ids (struct lexer *, const struct dictionary *dict, struct repeat_entry *, struct pool *);
91 static int parse_numbers (struct lexer *, struct repeat_entry *, struct pool *);
92 static int parse_strings (struct lexer *, struct repeat_entry *, struct pool *);
93
94 static void do_repeat_filter (struct string *line, void *block);
95 static bool do_repeat_read (struct string *line, char **file_name,
96                             int *line_number, void *block);
97 static void do_repeat_close (void *block);
98
99 int
100 cmd_do_repeat (struct lexer *lexer, struct dataset *ds)
101 {
102   struct repeat_block *block;
103
104   block = pool_create_container (struct repeat_block, pool);
105   block->ds = ds;
106
107   if (!parse_specification (lexer, block) || !parse_lines (lexer, block))
108     goto error;
109   
110   create_vars (block);
111   
112   block->cur_line = NULL;
113   block->loop_idx = -1;
114   getl_include_filter (do_repeat_filter, do_repeat_close, block);
115   getl_include_function (do_repeat_read, NULL, block);
116
117   return CMD_SUCCESS;
118
119  error:
120   pool_destroy (block->pool);
121   return CMD_CASCADING_FAILURE;
122 }
123
124 /* Parses the whole DO REPEAT command specification.
125    Returns success. */
126 static bool
127 parse_specification (struct lexer *lexer, struct repeat_block *block) 
128 {
129   char first_name[LONG_NAME_LEN + 1];
130
131   block->loop_cnt = 0;
132   block->macros = NULL;
133   do
134     {
135       struct repeat_entry *e;
136       struct repeat_entry *iter;
137       struct dictionary *dict = dataset_dict (block->ds);
138       int count;
139
140       /* Get a stand-in variable name and make sure it's unique. */
141       if (!lex_force_id (lexer))
142         return false;
143       if (dict_lookup_var (dict, lex_tokid (lexer)))
144         msg (SW, _("Dummy variable name \"%s\" hides dictionary "
145                    "variable \"%s\"."),
146              lex_tokid (lexer), lex_tokid (lexer));
147       for (iter = block->macros; iter != NULL; iter = iter->next)
148         if (!strcasecmp (iter->id, lex_tokid (lexer)))
149           {
150             msg (SE, _("Dummy variable name \"%s\" is given twice."), lex_tokid (lexer));
151             return false;
152           }
153
154       /* Make a new stand-in variable entry and link it into the
155          list. */
156       e = pool_alloc (block->pool, sizeof *e);
157       e->next = block->macros;
158       strcpy (e->id, lex_tokid (lexer));
159       block->macros = e;
160
161       /* Skip equals sign. */
162       lex_get (lexer);
163       if (!lex_force_match (lexer, '='))
164         return false;
165
166       /* Get the details of the variable's possible values. */
167       if (lex_token (lexer) == T_ID)
168         count = parse_ids (lexer, dict, e, block->pool);
169       else if (lex_is_number (lexer))
170         count = parse_numbers (lexer, e, block->pool);
171       else if (lex_token (lexer) == T_STRING)
172         count = parse_strings (lexer, e, block->pool);
173       else
174         {
175           lex_error (lexer, NULL);
176           return false;
177         }
178       if (count == 0)
179         return false;
180       if (lex_token (lexer) != '/' && lex_token (lexer) != '.') 
181         {
182           lex_error (lexer, NULL);
183           return false;
184         }
185
186       /* If this is the first variable then it defines how many
187          replacements there must be; otherwise enforce this number of
188          replacements. */
189       if (block->loop_cnt == 0)
190         {
191           block->loop_cnt = count;
192           strcpy (first_name, e->id);
193         }
194       else if (block->loop_cnt != count)
195         {
196           msg (SE, _("Dummy variable \"%s\" had %d "
197                      "substitutions, so \"%s\" must also, but %d "
198                      "were specified."),
199                first_name, block->loop_cnt, e->id, count);
200           return false;
201         }
202
203       lex_match (lexer, '/');
204     }
205   while (lex_token (lexer) != '.');
206
207   return true;
208 }
209
210 /* If KEYWORD appears beginning at CP, possibly preceded by white
211    space, returns a pointer to the character just after the
212    keyword.  Otherwise, returns a null pointer. */
213 static const char *
214 recognize_keyword (const char *cp, const char *keyword)
215 {
216   const char *end;
217
218   while (isspace ((unsigned char) *cp))
219     cp++;
220
221   end = lex_skip_identifier (cp);
222   if (end != cp
223       && lex_id_match_len (keyword, strlen (keyword), cp, end - cp))
224     return end;
225   else
226     return NULL;
227 }
228
229 /* Returns CP, advanced past a '+' or '-' if present. */
230 static const char *
231 skip_indentor (const char *cp) 
232 {
233   if (*cp == '+' || *cp == '-')
234     cp++;
235   return cp;
236 }
237
238 /* Returns true if LINE contains a DO REPEAT command, false
239    otherwise. */
240 static bool
241 recognize_do_repeat (const char *line) 
242 {
243   const char *cp = recognize_keyword (skip_indentor (line), "do");
244   return cp != NULL && recognize_keyword (cp, "repeat") != NULL;
245 }
246
247 /* Returns true if LINE contains an END REPEAT command, false
248    otherwise.  Sets *PRINT to true for END REPEAT PRINT, false
249    otherwise. */
250 static bool
251 recognize_end_repeat (const char *line, bool *print)
252 {
253   const char *cp = recognize_keyword (skip_indentor (line), "end");
254   if (cp == NULL)
255     return false;
256
257   cp = recognize_keyword (cp, "repeat");
258   if (cp == NULL) 
259     return false; 
260
261   *print = recognize_keyword (cp, "print");
262   return true; 
263 }
264
265 /* Read all the lines we are going to substitute, inside the DO
266    REPEAT...END REPEAT block. */
267 static bool
268 parse_lines (struct lexer *lexer, struct repeat_block *block) 
269 {
270   char *previous_file_name;
271   struct line_list **last_line;
272   int nesting_level;
273
274   previous_file_name = NULL;
275   block->first_line = NULL;
276   last_line = &block->first_line;
277   nesting_level = 0;
278
279   for (;;)
280     {
281       const char *cur_file_name;
282       int cur_line_number;
283       struct line_list *line;
284       struct string cur_line_copy;
285       bool dot;
286
287       if (! lex_get_line_raw (lexer))
288         return false;
289
290       /* If the current file has changed then record the fact. */
291       getl_location (&cur_file_name, &cur_line_number);
292       if (previous_file_name == NULL 
293           || !strcmp (cur_file_name, previous_file_name))
294         previous_file_name = pool_strdup (block->pool, cur_file_name);
295
296       ds_init_string (&cur_line_copy, lex_entire_line_ds (lexer) );
297       ds_rtrim (&cur_line_copy, ss_cstr (CC_SPACES));
298       dot = ds_chomp (&cur_line_copy, get_endcmd ());
299
300       if (recognize_do_repeat (ds_cstr (&cur_line_copy)))
301         nesting_level++; 
302       else if (recognize_end_repeat (ds_cstr (&cur_line_copy), &block->print)) 
303         {
304         if (nesting_level-- == 0)
305           {
306             lex_discard_line (lexer);
307             ds_destroy (&cur_line_copy);
308             return true;
309           } 
310         }
311       if (dot)
312         ds_put_char (&cur_line_copy, get_endcmd ());
313       
314       line = *last_line = pool_alloc (block->pool, sizeof *line);
315       line->next = NULL;
316       line->file_name = previous_file_name;
317       line->line_number = cur_line_number;
318       line->line = pool_strdup (block->pool, ds_cstr (&cur_line_copy) );
319       last_line = &line->next;
320
321       ds_destroy (&cur_line_copy);
322     }
323
324   lex_discard_line (lexer);
325   return true;
326 }
327
328 /* Creates variables for the given DO REPEAT. */
329 static void
330 create_vars (struct repeat_block *block)
331 {
332   struct repeat_entry *iter;
333  
334   for (iter = block->macros; iter; iter = iter->next)
335     if (iter->type == VAR_NAMES)
336       {
337         int i;
338
339         for (i = 0; i < block->loop_cnt; i++)
340           {
341             /* Ignore return value: if the variable already
342                exists there is no harm done. */
343             dict_create_var (dataset_dict (block->ds), iter->replacement[i], 0);
344           }
345       }
346 }
347
348 /* Parses a set of ids for DO REPEAT. */
349 static int
350 parse_ids (struct lexer *lexer, const struct dictionary *dict, 
351            struct repeat_entry *e, struct pool *pool)
352 {
353   size_t n = 0;
354   e->type = VAR_NAMES;
355   return parse_mixed_vars_pool (lexer, dict, pool, 
356                                 &e->replacement, &n, PV_NONE) ? n : 0;
357 }
358
359 /* Adds STRING to E's list of replacements, which has *USED
360    elements and has room for *ALLOCATED.  Allocates memory from
361    POOL. */
362 static void
363 add_replacement (char *string,
364                  struct repeat_entry *e, struct pool *pool,
365                  size_t *used, size_t *allocated) 
366 {
367   if (*used == *allocated)
368     e->replacement = pool_2nrealloc (pool, e->replacement, allocated,
369                                      sizeof *e->replacement);
370   e->replacement[(*used)++] = string;
371 }
372
373 /* Parses a list of numbers for DO REPEAT. */
374 static int
375 parse_numbers (struct lexer *lexer, struct repeat_entry *e, struct pool *pool)
376 {
377   size_t used = 0;
378   size_t allocated = 0;
379   
380   e->type = OTHER;
381   e->replacement = NULL;
382
383   do
384     {
385       long a, b, i;
386
387       /* Parse A TO B into a, b. */
388       if (!lex_force_int (lexer))
389         return 0;
390       a = lex_integer (lexer);
391
392       lex_get (lexer);
393       if (lex_token (lexer) == T_TO)
394         {
395           lex_get (lexer);
396           if (!lex_force_int (lexer))
397             return 0;
398           b = lex_integer (lexer);
399           if (b < a) 
400             {
401               msg (SE, _("%ld TO %ld is an invalid range."), a, b);
402               return 0;
403             }
404           lex_get (lexer);
405         }
406       else
407         b = a;
408
409       for (i = a; i <= b; i++)
410         add_replacement (pool_asprintf (pool, "%ld", i),
411                          e, pool, &used, &allocated);
412
413
414       lex_match (lexer, ',');
415     }
416   while (lex_token (lexer) != '/' && lex_token (lexer) != '.');
417
418   return used;
419 }
420
421 /* Parses a list of strings for DO REPEAT. */
422 int
423 parse_strings (struct lexer *lexer, struct repeat_entry *e, struct pool *pool)
424 {
425   size_t used = 0;
426   size_t allocated = 0;
427   
428   e->type = OTHER;
429   e->replacement = NULL;
430
431   do
432     {
433       char *string;
434       
435       if (lex_token (lexer) != T_STRING)
436         {
437           msg (SE, _("String expected."));
438           return 0;
439         }
440
441       string = lex_token_representation (lexer);
442       pool_register (pool, free, string);
443       add_replacement (string, e, pool, &used, &allocated);
444
445       lex_get (lexer);
446       lex_match (lexer, ',');
447     }
448   while (lex_token (lexer) != '/' && lex_token (lexer) != '.');
449
450   return used;
451 }
452 \f
453 int
454 cmd_end_repeat (struct lexer *lexer UNUSED, struct dataset *ds UNUSED)
455 {
456   msg (SE, _("No matching DO REPEAT."));
457   return CMD_CASCADING_FAILURE;
458 }
459 \f
460 /* Finds a DO REPEAT macro with name MACRO_NAME and returns the
461    appropriate subsitution if found, or NULL if not. */
462 static char *
463 find_substitution (struct repeat_block *block, const char *name, size_t length)
464 {
465   struct repeat_entry *e;
466
467   for (e = block->macros; e; e = e->next)
468     if (!memcasecmp (e->id, name, length) && strlen (e->id) == length)
469       return e->replacement[block->loop_idx];
470   
471   return NULL;
472 }
473
474 /* Makes appropriate DO REPEAT macro substitutions within the 
475    repeated lines. */
476 static void
477 do_repeat_filter (struct string *line, void *block_)
478 {
479   struct repeat_block *block = block_;
480   bool in_apos, in_quote;
481   char *cp;
482   struct string output;
483   bool dot;
484
485   ds_init_empty (&output);
486
487   /* Strip trailing whitespace, check for & remove terminal dot. */
488   while (isspace (ds_last (line)))
489     ds_truncate (line, ds_length (line) - 1);
490   dot = ds_chomp (line, get_endcmd ());
491
492   in_apos = in_quote = false;
493   for (cp = ds_cstr (line); cp < ds_end (line); )
494     {
495       if (*cp == '\'' && !in_quote)
496         in_apos = !in_apos;
497       else if (*cp == '"' && !in_apos)
498         in_quote = !in_quote;
499       
500       if (in_quote || in_apos || !lex_is_id1 (*cp))
501         ds_put_char (&output, *cp++);
502       else 
503         {
504           const char *start = cp;
505           char *end = lex_skip_identifier (start);
506           const char *substitution = find_substitution (block,
507                                                         start, end - start);
508           if (substitution != NULL) 
509             ds_put_cstr (&output, substitution);
510           else
511             ds_put_substring (&output, ss_buffer (start, end - start));
512           cp = end;
513         }
514     }
515   if (dot)
516     ds_put_char (&output, get_endcmd ());
517
518   ds_swap (line, &output);
519   ds_destroy (&output);
520 }
521
522 /* Function called by getl to read a line.
523    Puts the line in OUTPUT, sets the file name in *FILE_NAME and
524    line number in *LINE_NUMBER.  Returns true if a line was
525    obtained, false if the source is exhausted. */
526 static bool
527 do_repeat_read (struct string *output, char **file_name, int *line_number,
528                 void *block_) 
529 {
530   struct repeat_block *block = block_;
531   struct line_list *line;
532
533   if (block->cur_line == NULL) 
534     {
535       block->loop_idx++;
536       if (block->loop_idx >= block->loop_cnt)
537         return false;
538       block->cur_line = block->first_line;
539     }
540   line = block->cur_line;
541
542   ds_assign_cstr (output, line->line);
543   *file_name = line->file_name;
544   *line_number = -line->line_number;
545   block->cur_line = line->next;
546   return true;
547 }
548
549 /* Frees a DO REPEAT block.
550    Called by getl to close out the DO REPEAT block. */
551 static void
552 do_repeat_close (void *block_)
553 {
554   struct repeat_block *block = block_;
555   pool_destroy (block->pool);
556 }