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