Clean up how transformations work.
[pspp] / src / language / control / do-if.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2009-2012 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <stdlib.h>
20
21 #include "data/dataset.h"
22 #include "data/transformations.h"
23 #include "language/command.h"
24 #include "language/data-io/inpt-pgm.h"
25 #include "language/expressions/public.h"
26 #include "language/lexer/lexer.h"
27 #include "libpspp/compiler.h"
28 #include "libpspp/message.h"
29 #include "libpspp/str.h"
30
31 #include "gl/xalloc.h"
32
33 #include "gettext.h"
34 #define _(msgid) gettext (msgid)
35
36 /* A conditional clause. */
37 struct clause
38   {
39     struct msg_location *location;
40     struct expression *condition; /* Test expression; NULL for ELSE clause. */
41     struct trns_chain xforms;
42   };
43
44 /* DO IF transformation. */
45 struct do_if_trns
46   {
47     struct clause *clauses;     /* Clauses. */
48     size_t n_clauses;           /* Number of clauses. */
49
50     const struct trns_chain *resume;
51     size_t ofs;
52   };
53
54 static const struct trns_class do_if_trns_class;
55
56 static void
57 start_clause (struct lexer *lexer, struct dataset *ds,
58               bool condition, struct do_if_trns *do_if,
59               size_t *allocated_clauses, bool *ok)
60 {
61   if (*ok && do_if->n_clauses > 0
62       && !do_if->clauses[do_if->n_clauses - 1].condition)
63     {
64       if (condition)
65         msg (SE, _("ELSE IF is not allowed following ELSE "
66                    "within DO IF...END IF."));
67       else
68         msg (SE, _("Only one ELSE is allowed within DO IF...END IF."));
69
70       msg_at (SN, do_if->clauses[do_if->n_clauses - 1].location,
71               _("This is the location of the previous ELSE clause."));
72
73       msg_at (SN, do_if->clauses[0].location,
74               _("This is the location of the DO IF command."));
75
76       *ok = false;
77     }
78
79   if (do_if->n_clauses >= *allocated_clauses)
80     do_if->clauses = x2nrealloc (do_if->clauses, allocated_clauses,
81                                  sizeof *do_if->clauses);
82   struct clause *clause = &do_if->clauses[do_if->n_clauses++];
83
84   *clause = (struct clause) { .location = NULL };
85   if (condition)
86     {
87       clause->condition = expr_parse_bool (lexer, ds);
88       if (!clause->condition)
89         lex_discard_rest_of_command (lexer);
90     }
91   clause->location = lex_ofs_location (lexer, 0, lex_ofs (lexer));
92
93   lex_end_of_command (lexer);
94   lex_get (lexer);
95
96   proc_push_transformations (ds);
97 }
98
99 static void
100 finish_clause (struct dataset *ds, struct do_if_trns *do_if)
101 {
102   struct clause *clause = &do_if->clauses[do_if->n_clauses - 1];
103   proc_pop_transformations (ds, &clause->xforms);
104 }
105
106 /* Parse DO IF. */
107 int
108 cmd_do_if (struct lexer *lexer, struct dataset *ds)
109 {
110   struct do_if_trns *do_if = xmalloc (sizeof *do_if);
111   *do_if = (struct do_if_trns) { .n_clauses = 0 };
112
113   size_t allocated_clauses = 0;
114   bool ok = true;
115
116   start_clause (lexer, ds, true, do_if, &allocated_clauses, &ok);
117   while (!lex_match_phrase (lexer, "END IF"))
118     {
119       if (lex_token (lexer) == T_STOP)
120         {
121           lex_error (lexer, NULL);
122           break;
123         }
124       else if (lex_match_phrase (lexer, "ELSE IF"))
125         {
126           finish_clause (ds, do_if);
127           start_clause (lexer, ds, true, do_if, &allocated_clauses, &ok);
128         }
129       else if (lex_match_id (lexer, "ELSE"))
130         {
131           finish_clause (ds, do_if);
132           start_clause (lexer, ds, false, do_if, &allocated_clauses, &ok);
133         }
134       else
135         cmd_parse_in_state (lexer, ds,
136                             (in_input_program ()
137                              ? CMD_STATE_NESTED_INPUT_PROGRAM
138                              : CMD_STATE_NESTED_DATA));
139     }
140   finish_clause (ds, do_if);
141
142   add_transformation (ds, &do_if_trns_class, do_if);
143
144   return ok ? CMD_SUCCESS : CMD_FAILURE;
145 }
146
147 int
148 cmd_inside_do_if (struct lexer *lexer UNUSED, struct dataset *ds UNUSED)
149 {
150   msg (SE, _("This command cannot appear outside DO IF...END IF."));
151   return CMD_FAILURE;
152 }
153
154 static const struct trns_chain *
155 do_if_find_clause (const struct do_if_trns *do_if,
156                    struct ccase *c, casenumber case_num)
157 {
158   for (size_t i = 0; i < do_if->n_clauses; i++)
159     {
160       const struct clause *clause = &do_if->clauses[i];
161       if (!clause->condition)
162         return &clause->xforms;
163
164       double boolean = expr_evaluate_num (clause->condition, c, case_num);
165       if (boolean != 0.0)
166         return boolean == SYSMIS ? NULL : &clause->xforms;
167     }
168   return NULL;
169 }
170
171 static enum trns_result
172 do_if_trns_proc (void *do_if_, struct ccase **c, casenumber case_num)
173 {
174   struct do_if_trns *do_if = do_if_;
175
176   const struct trns_chain *chain;
177   size_t start;
178   if (do_if->resume)
179     {
180       chain = do_if->resume;
181       start = do_if->ofs;
182       do_if->resume = NULL;
183       do_if->ofs = 0;
184     }
185   else
186     {
187       chain = do_if_find_clause (do_if, *c, case_num);
188       if (!chain)
189         return TRNS_CONTINUE;
190       start = 0;
191     }
192
193   for (size_t i = start; i < chain->n; i++)
194     {
195       const struct transformation *trns = &chain->xforms[i];
196       enum trns_result r = trns->class->execute (trns->aux, c, case_num);
197       switch (r)
198         {
199         case TRNS_CONTINUE:
200           break;
201
202         case TRNS_BREAK:
203         case TRNS_DROP_CASE:
204         case TRNS_ERROR:
205         case TRNS_END_FILE:
206           return r;
207
208         case TRNS_END_CASE:
209           do_if->resume = chain;
210           do_if->ofs = i;
211           return r;
212         }
213     }
214   return TRNS_CONTINUE;
215 }
216
217 static bool
218 do_if_trns_free (void *do_if_)
219 {
220   struct do_if_trns *do_if = do_if_;
221
222   for (size_t i = 0; i < do_if->n_clauses; i++)
223     {
224       struct clause *clause = &do_if->clauses[i];
225
226       msg_location_destroy (clause->location);
227       expr_free (clause->condition);
228
229       trns_chain_uninit (&clause->xforms);
230     }
231   free (do_if->clauses);
232   free (do_if);
233   return true;
234 }
235
236 static const struct trns_class do_if_trns_class = {
237   .name = "DO IF",
238   .execute = do_if_trns_proc,
239   .destroy = do_if_trns_free,
240 };