work on making transformations use relative return values
[pspp] / src / language / control / do-if.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2009-2013 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/case.h"
22 #include "data/control-stack.h"
23 #include "data/dataset.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/compiler.h"
30 #include "libpspp/message.h"
31 #include "libpspp/str.h"
32
33 #include "gl/xalloc.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 DO_IF_index;            /* DO IF transformation. */
80     int past_END_IF_index;      /* Transformation just past last clause. */
81   };
82
83 /* Jumps past the END IF.  */
84 struct end_if_trns
85   {
86     struct do_if_trns *do_if;
87     int index;
88   };
89
90 static const struct ctl_class do_if_class;
91
92 static int parse_clause (struct lexer *, struct do_if_trns *, struct dataset *ds);
93 static void add_clause (struct do_if_trns *, struct expression *condition);
94 static void add_else (struct do_if_trns *);
95
96 static bool has_else (struct do_if_trns *);
97 static bool must_not_have_else (struct do_if_trns *);
98 static void close_do_if (void *do_if);
99
100 static trns_proc_func do_if_trns_proc, end_if_trns_proc;
101 static trns_free_func do_if_trns_free, end_if_trns_free;
102
103 /* Parse DO IF. */
104 int
105 cmd_do_if (struct lexer *lexer, struct dataset *ds)
106 {
107   struct do_if_trns *do_if = xmalloc (sizeof *do_if);
108   do_if->clauses = NULL;
109   do_if->clause_cnt = 0;
110   do_if->ds = ds;
111   do_if->DO_IF_index = next_transformation (ds);
112
113   ctl_stack_push (&do_if_class, do_if);
114   add_transformation (ds, do_if_trns_proc, do_if_trns_free, do_if);
115
116   return parse_clause (lexer, do_if, ds);
117 }
118
119 /* Parse ELSE IF. */
120 int
121 cmd_else_if (struct lexer *lexer, struct dataset *ds)
122 {
123   struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
124   if (do_if == NULL || !must_not_have_else (do_if))
125     return CMD_CASCADING_FAILURE;
126   return parse_clause (lexer, do_if, ds);
127 }
128
129 /* Parse ELSE. */
130 int
131 cmd_else (struct lexer *lexer UNUSED, struct dataset *ds)
132 {
133   struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
134   assert (ds == do_if->ds);
135   if (do_if == NULL || !must_not_have_else (do_if))
136     return CMD_CASCADING_FAILURE;
137   add_else (do_if);
138   return CMD_SUCCESS;
139 }
140
141 /* Parse END IF. */
142 int
143 cmd_end_if (struct lexer *lexer UNUSED, struct dataset *ds)
144 {
145   struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
146
147   if (do_if == NULL)
148     return CMD_CASCADING_FAILURE;
149
150   assert (ds == do_if->ds);
151   ctl_stack_pop (do_if);
152
153   return CMD_SUCCESS;
154 }
155
156 /* Closes out DO_IF, by adding a sentinel ELSE clause if
157    necessary and setting past_END_IF_index. */
158 static void
159 close_do_if (void *do_if_)
160 {
161   struct do_if_trns *do_if = do_if_;
162
163   if (!has_else (do_if))
164     add_else (do_if);
165   do_if->past_END_IF_index = next_transformation (do_if->ds);
166 }
167
168 /* Adds an ELSE clause to DO_IF pointing to the next
169    transformation. */
170 static void
171 add_else (struct do_if_trns *do_if)
172 {
173   assert (!has_else (do_if));
174   add_clause (do_if, NULL);
175 }
176
177 /* Returns true if DO_IF does not yet have an ELSE clause.
178    Reports an error and returns false if it does already. */
179 static bool
180 must_not_have_else (struct do_if_trns *do_if)
181 {
182   if (has_else (do_if))
183     {
184       msg (SE, _("This command may not follow %s in %s ... %s."), "ELSE", "DO IF", "END IF");
185       return false;
186     }
187   else
188     return true;
189 }
190
191 /* Returns true if DO_IF already has an ELSE clause,
192    false otherwise. */
193 static bool
194 has_else (struct do_if_trns *do_if)
195 {
196   return (do_if->clause_cnt != 0
197           && do_if->clauses[do_if->clause_cnt - 1].condition == NULL);
198 }
199
200 /* Parses a DO IF or ELSE IF expression and appends the
201    corresponding clause to DO_IF.  Checks for end of command and
202    returns a command return code. */
203 static int
204 parse_clause (struct lexer *lexer, struct do_if_trns *do_if, struct dataset *ds)
205 {
206   struct expression *condition;
207
208   condition = expr_parse (lexer, ds, EXPR_BOOLEAN);
209   if (condition == NULL)
210     return CMD_CASCADING_FAILURE;
211
212   add_clause (do_if, condition);
213
214   return CMD_SUCCESS;
215 }
216
217 /* Adds a clause to DO_IF that tests for the given CONDITION and,
218    if true, jumps to the set of transformations produced by
219    following commands. */
220 static void
221 add_clause (struct do_if_trns *do_if, struct expression *condition)
222 {
223   struct clause *clause;
224
225   if (do_if->clause_cnt > 0)
226     {
227       struct end_if_trns *end_if;
228
229       end_if = xmalloc (sizeof *end_if);
230       end_if->do_if = do_if;
231       end_if->index = next_transformation (do_if->ds);
232       add_transformation (do_if->ds,
233                           end_if_trns_proc, end_if_trns_free, end_if);
234     }
235
236   do_if->clauses = xnrealloc (do_if->clauses,
237                               do_if->clause_cnt + 1, sizeof *do_if->clauses);
238   clause = &do_if->clauses[do_if->clause_cnt++];
239   clause->condition = condition;
240   clause->target_index = next_transformation (do_if->ds);
241 }
242
243 /* DO IF transformation procedure.
244    Checks each clause and jumps to the appropriate
245    transformation. */
246 static int
247 do_if_trns_proc (void *do_if_, struct ccase **c, casenumber case_num UNUSED)
248 {
249   struct do_if_trns *do_if = do_if_;
250   struct clause *clause;
251
252   for (clause = do_if->clauses; clause < do_if->clauses + do_if->clause_cnt;
253        clause++)
254     {
255       if (clause->condition != NULL)
256         {
257           double boolean = expr_evaluate_num (clause->condition, *c, case_num);
258           if (boolean == 1.0)
259             return clause->target_index - do_if->DO_IF_index;
260           else if (boolean == SYSMIS)
261             return do_if->past_END_IF_index - do_if->DO_IF_index;
262         }
263       else
264         return clause->target_index - do_if->DO_IF_index;
265     }
266   return do_if->past_END_IF_index;
267 }
268
269 /* Frees a DO IF transformation. */
270 static bool
271 do_if_trns_free (void *do_if_)
272 {
273   struct do_if_trns *do_if = do_if_;
274   struct clause *clause;
275
276   for (clause = do_if->clauses; clause < do_if->clauses + do_if->clause_cnt;
277        clause++)
278     expr_free (clause->condition);
279   free (do_if->clauses);
280   free (do_if);
281   return true;
282 }
283
284 /* Breaks out of a DO IF construct. */
285 static int
286 end_if_trns_proc (void *end_if_, struct ccase **c UNUSED,
287                  casenumber case_num UNUSED)
288 {
289   struct end_if_trns *end_if = end_if_;
290
291   return end_if->do_if->past_END_IF_index - end_if->index;
292 }
293
294 /* Frees an END IF transformation. */
295 static bool
296 end_if_trns_free (void *end_if_)
297 {
298   struct end_if_trns *end_if = end_if_;
299
300   free (end_if);
301   return true;
302 }
303 /* DO IF control structure class definition. */
304 static const struct ctl_class do_if_class =
305   {
306     "DO IF",
307     "END IF",
308     close_do_if,
309   };