Added new files resulting from directory restructuring.
[pspp-builds.git] / src / language / control / do-if.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, USA. */
19
20 #include <config.h>
21 #include "control-stack.h"
22 #include "message.h"
23 #include <stdlib.h>
24 #include "alloc.h"
25 #include "command.h"
26 #include "message.h"
27 #include "expressions/public.h"
28 #include "lexer.h"
29 #include "str.h"
30 #include "variable.h"
31
32 #include "gettext.h"
33 #define _(msgid) gettext (msgid)
34
35 /* DO IF, ELSE IF, and ELSE are translated as a single
36    transformation that evaluates each condition and jumps to the
37    start of the appropriate block of transformations.  Each block
38    of transformations (except for the last) ends with a
39    transformation that jumps past the remaining blocks.
40
41    So, the following code:
42
43        DO IF a.             
44        ...block 1...
45        ELSE IF b.
46        ...block 2...
47        ELSE.
48        ...block 3...
49        END IF.
50
51    is effectively translated like this:
52
53        IF a GOTO 1, IF b GOTO 2, ELSE GOTO 3.
54        1: ...block 1...
55           GOTO 4
56        2: ...block 2...
57           GOTO 4
58        3: ...block 3...
59        4:
60
61 */
62
63 /* A conditional clause. */
64 struct clause 
65   {
66     struct expression *condition; /* Test expression; NULL for ELSE clause. */
67     int target_index;           /* Transformation to jump to if true. */
68   };
69
70 /* DO IF transformation. */
71 struct do_if_trns
72   {
73     struct clause *clauses;     /* Clauses. */
74     size_t clause_cnt;          /* Number of clauses. */
75     int past_END_IF_index;      /* Transformation just past last clause. */
76   };
77
78 static struct ctl_class do_if_class;
79
80 static int parse_clause (struct do_if_trns *);
81 static void add_clause (struct do_if_trns *,
82                         struct expression *condition, int target_index);
83 static void add_else (struct do_if_trns *);
84
85 static bool has_else (struct do_if_trns *);
86 static bool must_not_have_else (struct do_if_trns *);
87 static void close_do_if (void *do_if);
88
89 static trns_proc_func do_if_trns_proc, break_trns_proc;
90 static trns_free_func do_if_trns_free;
91
92 /* Parse DO IF. */
93 int
94 cmd_do_if (void)
95 {
96   struct do_if_trns *do_if = xmalloc (sizeof *do_if);
97   do_if->clauses = NULL;
98   do_if->clause_cnt = 0;
99
100   ctl_stack_push (&do_if_class, do_if);
101   add_transformation (do_if_trns_proc, do_if_trns_free, do_if);
102
103   return parse_clause (do_if);
104 }
105
106 /* Parse ELSE IF. */
107 int
108 cmd_else_if (void)
109 {
110   struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
111   if (do_if == NULL || !must_not_have_else (do_if))
112     return CMD_CASCADING_FAILURE;
113   return parse_clause (do_if);
114 }
115
116 /* Parse ELSE. */
117 int
118 cmd_else (void)
119 {
120   struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
121   if (do_if == NULL || !must_not_have_else (do_if))
122     return CMD_CASCADING_FAILURE;
123   add_else (do_if);
124   return lex_end_of_command ();
125 }
126
127 /* Parse END IF. */
128 int
129 cmd_end_if (void)
130 {
131   struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
132   if (do_if == NULL)
133     return CMD_CASCADING_FAILURE;
134
135   ctl_stack_pop (do_if);
136
137   return lex_end_of_command ();
138 }
139
140 /* Closes out DO_IF, by adding a sentinel ELSE clause if
141    necessary and setting past_END_IF_index. */
142 static void
143 close_do_if (void *do_if_) 
144 {
145   struct do_if_trns *do_if = do_if_;
146   
147   if (!has_else (do_if)) 
148     add_else (do_if);
149   do_if->past_END_IF_index = next_transformation ();
150 }
151
152 /* Adds an ELSE clause to DO_IF pointing to the next
153    transformation. */
154 static void
155 add_else (struct do_if_trns *do_if) 
156 {
157   assert (!has_else (do_if));
158   add_clause (do_if, NULL, next_transformation ());
159 }
160
161 /* Returns true if DO_IF does not yet have an ELSE clause.
162    Reports an error and returns false if it does already. */
163 static bool
164 must_not_have_else (struct do_if_trns *do_if) 
165 {
166   if (has_else (do_if))
167     {
168       msg (SE, _("This command may not follow ELSE in DO IF...END IF."));
169       return false;
170     }
171   else
172     return true;
173 }
174
175 /* Returns true if DO_IF already has an ELSE clause,
176    false otherwise. */
177 static bool
178 has_else (struct do_if_trns *do_if) 
179 {
180   return (do_if->clause_cnt != 0
181           && do_if->clauses[do_if->clause_cnt - 1].condition == NULL);
182 }
183
184 /* Parses a DO IF or ELSE IF expression and appends the
185    corresponding clause to DO_IF.  Checks for end of command and
186    returns a command return code. */
187 static int
188 parse_clause (struct do_if_trns *do_if)
189 {
190   struct expression *condition;
191
192   condition = expr_parse (default_dict, EXPR_BOOLEAN);
193   if (condition == NULL)
194     return CMD_CASCADING_FAILURE;
195
196   add_clause (do_if, condition, next_transformation ());
197
198   return lex_end_of_command ();
199 }
200
201 /* Adds a clause to DO_IF that tests for the given CONDITION and,
202    if true, jumps to TARGET_INDEX. */
203 static void
204 add_clause (struct do_if_trns *do_if,
205             struct expression *condition, int target_index) 
206 {
207   struct clause *clause;
208
209   if (do_if->clause_cnt > 0)
210     add_transformation (break_trns_proc, NULL, do_if);
211
212   do_if->clauses = xnrealloc (do_if->clauses,
213                               do_if->clause_cnt + 1, sizeof *do_if->clauses);
214   clause = &do_if->clauses[do_if->clause_cnt++];
215   clause->condition = condition;
216   clause->target_index = target_index;
217 }
218
219 /* DO IF transformation procedure.
220    Checks each clause and jumps to the appropriate
221    transformation. */
222 static int 
223 do_if_trns_proc (void *do_if_, struct ccase *c, int case_num UNUSED)
224 {
225   struct do_if_trns *do_if = do_if_;
226   struct clause *clause;
227
228   for (clause = do_if->clauses; clause < do_if->clauses + do_if->clause_cnt;
229        clause++) 
230     {
231       if (clause->condition != NULL)
232         {
233           double boolean = expr_evaluate_num (clause->condition, c, case_num);
234           if (boolean == 1.0)
235             return clause->target_index;
236           else if (boolean == SYSMIS)
237             return do_if->past_END_IF_index;
238         }
239       else 
240         return clause->target_index;
241     }
242   return do_if->past_END_IF_index;
243 }
244
245 /* Frees a DO IF transformation. */
246 static bool
247 do_if_trns_free (void *do_if_)
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     expr_free (clause->condition);
255   free (do_if->clauses);
256   free (do_if);
257   return true;
258 }
259
260 /* Breaks out of a DO IF construct. */
261 static int 
262 break_trns_proc (void *do_if_, struct ccase *c UNUSED, int case_num UNUSED)
263 {
264   struct do_if_trns *do_if = do_if_;
265
266   return do_if->past_END_IF_index;
267 }
268
269 /* DO IF control structure class definition. */
270 static struct ctl_class do_if_class = 
271   {
272     "DO IF",
273     "END IF",
274     close_do_if,
275   };