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