Change license from GPLv2+ to GPLv3+.
[pspp-builds.git] / src / language / control / do-if.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000 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 "control-stack.h"
22 #include <data/case.h>
23 #include <data/procedure.h>
24 #include <data/transformations.h>
25 #include <data/value.h>
26 #include <language/command.h>
27 #include <language/expressions/public.h>
28 #include <language/lexer/lexer.h>
29 #include <libpspp/alloc.h>
30 #include <libpspp/compiler.h>
31 #include <libpspp/message.h>
32 #include <libpspp/message.h>
33 #include <libpspp/str.h>
34
35 #include "gettext.h"
36 #define _(msgid) gettext (msgid)
37
38 /* DO IF, ELSE IF, and ELSE are translated as a single
39    transformation that evaluates each condition and jumps to the
40    start of the appropriate block of transformations.  Each block
41    of transformations (except for the last) ends with a
42    transformation that jumps past the remaining blocks.
43
44    So, the following code:
45
46        DO IF a.
47        ...block 1...
48        ELSE IF b.
49        ...block 2...
50        ELSE.
51        ...block 3...
52        END IF.
53
54    is effectively translated like this:
55
56        IF a GOTO 1, IF b GOTO 2, ELSE GOTO 3.
57        1: ...block 1...
58           GOTO 4
59        2: ...block 2...
60           GOTO 4
61        3: ...block 3...
62        4:
63
64 */
65
66 /* A conditional clause. */
67 struct clause
68   {
69     struct expression *condition; /* Test expression; NULL for ELSE clause. */
70     int target_index;           /* Transformation to jump to if true. */
71   };
72
73 /* DO IF transformation. */
74 struct do_if_trns
75   {
76     struct dataset *ds;         /* The dataset */
77     struct clause *clauses;     /* Clauses. */
78     size_t clause_cnt;          /* Number of clauses. */
79     int past_END_IF_index;      /* Transformation just past last clause. */
80   };
81
82 static const struct ctl_class do_if_class;
83
84 static int parse_clause (struct lexer *, struct do_if_trns *, struct dataset *ds);
85 static void add_clause (struct do_if_trns *,
86                         struct expression *condition, int target_index);
87 static void add_else (struct do_if_trns *);
88
89 static bool has_else (struct do_if_trns *);
90 static bool must_not_have_else (struct do_if_trns *);
91 static void close_do_if (void *do_if);
92
93 static trns_finalize_func do_if_finalize_func;
94 static trns_proc_func do_if_trns_proc, break_trns_proc;
95 static trns_free_func do_if_trns_free;
96
97 /* Parse DO IF. */
98 int
99 cmd_do_if (struct lexer *lexer, struct dataset *ds)
100 {
101   struct do_if_trns *do_if = xmalloc (sizeof *do_if);
102   do_if->clauses = NULL;
103   do_if->clause_cnt = 0;
104   do_if->ds = ds;
105
106   ctl_stack_push (&do_if_class, do_if);
107   add_transformation_with_finalizer (ds, do_if_finalize_func,
108                                      do_if_trns_proc, do_if_trns_free, do_if);
109
110   return parse_clause (lexer, do_if, ds);
111 }
112
113 /* Parse ELSE IF. */
114 int
115 cmd_else_if (struct lexer *lexer, struct dataset *ds)
116 {
117   struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
118   if (do_if == NULL || !must_not_have_else (do_if))
119     return CMD_CASCADING_FAILURE;
120   return parse_clause (lexer, do_if, ds);
121 }
122
123 /* Parse ELSE. */
124 int
125 cmd_else (struct lexer *lexer, struct dataset *ds)
126 {
127   struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
128   assert (ds == do_if->ds);
129   if (do_if == NULL || !must_not_have_else (do_if))
130     return CMD_CASCADING_FAILURE;
131   add_else (do_if);
132   return lex_end_of_command (lexer);
133 }
134
135 /* Parse END IF. */
136 int
137 cmd_end_if (struct lexer *lexer, struct dataset *ds)
138 {
139   struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
140   assert (ds == do_if->ds);
141
142   if (do_if == NULL)
143     return CMD_CASCADING_FAILURE;
144
145   ctl_stack_pop (do_if);
146
147   return lex_end_of_command (lexer);
148 }
149
150 /* Closes out DO_IF, by adding a sentinel ELSE clause if
151    necessary and setting past_END_IF_index. */
152 static void
153 close_do_if (void *do_if_)
154 {
155   struct do_if_trns *do_if = do_if_;
156
157   if (!has_else (do_if))
158     add_else (do_if);
159   do_if->past_END_IF_index = next_transformation (do_if->ds);
160 }
161
162 /* Adds an ELSE clause to DO_IF pointing to the next
163    transformation. */
164 static void
165 add_else (struct do_if_trns *do_if)
166 {
167   assert (!has_else (do_if));
168   add_clause (do_if, NULL, next_transformation (do_if->ds));
169 }
170
171 /* Returns true if DO_IF does not yet have an ELSE clause.
172    Reports an error and returns false if it does already. */
173 static bool
174 must_not_have_else (struct do_if_trns *do_if)
175 {
176   if (has_else (do_if))
177     {
178       msg (SE, _("This command may not follow ELSE in DO IF...END IF."));
179       return false;
180     }
181   else
182     return true;
183 }
184
185 /* Returns true if DO_IF already has an ELSE clause,
186    false otherwise. */
187 static bool
188 has_else (struct do_if_trns *do_if)
189 {
190   return (do_if->clause_cnt != 0
191           && do_if->clauses[do_if->clause_cnt - 1].condition == NULL);
192 }
193
194 /* Parses a DO IF or ELSE IF expression and appends the
195    corresponding clause to DO_IF.  Checks for end of command and
196    returns a command return code. */
197 static int
198 parse_clause (struct lexer *lexer, struct do_if_trns *do_if, struct dataset *ds)
199 {
200   struct expression *condition;
201
202   condition = expr_parse (lexer, ds, EXPR_BOOLEAN);
203   if (condition == NULL)
204     return CMD_CASCADING_FAILURE;
205
206   add_clause (do_if, condition, next_transformation (ds));
207
208   return lex_end_of_command (lexer);
209 }
210
211 /* Adds a clause to DO_IF that tests for the given CONDITION and,
212    if true, jumps to TARGET_INDEX. */
213 static void
214 add_clause (struct do_if_trns *do_if,
215             struct expression *condition, int target_index)
216 {
217   struct clause *clause;
218
219   if (do_if->clause_cnt > 0)
220     add_transformation (do_if->ds, break_trns_proc, NULL, do_if);
221
222   do_if->clauses = xnrealloc (do_if->clauses,
223                               do_if->clause_cnt + 1, sizeof *do_if->clauses);
224   clause = &do_if->clauses[do_if->clause_cnt++];
225   clause->condition = condition;
226   clause->target_index = target_index;
227 }
228
229 /* Finalizes DO IF by clearing the control stack, thus ensuring
230    that all open DO IFs are closed. */
231 static void
232 do_if_finalize_func (void *do_if_ UNUSED)
233 {
234   /* This will be called multiple times if multiple DO IFs were
235      executed, which is slightly unclean, but at least it's
236      idempotent. */
237   ctl_stack_clear ();
238 }
239
240 /* DO IF transformation procedure.
241    Checks each clause and jumps to the appropriate
242    transformation. */
243 static int
244 do_if_trns_proc (void *do_if_, struct ccase *c, casenumber case_num UNUSED)
245 {
246   struct do_if_trns *do_if = do_if_;
247   struct clause *clause;
248
249   for (clause = do_if->clauses; clause < do_if->clauses + do_if->clause_cnt;
250        clause++)
251     {
252       if (clause->condition != NULL)
253         {
254           double boolean = expr_evaluate_num (clause->condition, c, case_num);
255           if (boolean == 1.0)
256             return clause->target_index;
257           else if (boolean == SYSMIS)
258             return do_if->past_END_IF_index;
259         }
260       else
261         return clause->target_index;
262     }
263   return do_if->past_END_IF_index;
264 }
265
266 /* Frees a DO IF transformation. */
267 static bool
268 do_if_trns_free (void *do_if_)
269 {
270   struct do_if_trns *do_if = do_if_;
271   struct clause *clause;
272
273   for (clause = do_if->clauses; clause < do_if->clauses + do_if->clause_cnt;
274        clause++)
275     expr_free (clause->condition);
276   free (do_if->clauses);
277   free (do_if);
278   return true;
279 }
280
281 /* Breaks out of a DO IF construct. */
282 static int
283 break_trns_proc (void *do_if_, struct ccase *c UNUSED, casenumber case_num UNUSED)
284 {
285   struct do_if_trns *do_if = do_if_;
286
287   return do_if->past_END_IF_index;
288 }
289
290 /* DO IF control structure class definition. */
291 static const struct ctl_class do_if_class =
292   {
293     "DO IF",
294     "END IF",
295     close_do_if,
296   };