18 |
02110-1301, USA. */ |
02110-1301, USA. */ |
19 |
|
|
20 |
#include <config.h> |
#include <config.h> |
21 |
#include "do-ifP.h" |
#include "ctl-stack.h" |
22 |
#include "error.h" |
#include "error.h" |
23 |
#include <stdlib.h> |
#include <stdlib.h> |
24 |
#include "alloc.h" |
#include "alloc.h" |
32 |
#include "gettext.h" |
#include "gettext.h" |
33 |
#define _(msgid) gettext (msgid) |
#define _(msgid) gettext (msgid) |
34 |
|
|
35 |
#include "debug-print.h" |
/* DO IF, ELSE IF, and ELSE are translated as a single |
36 |
|
transformation that evaluates each condition and jumps to the |
37 |
/* *INDENT-OFF* */ |
start of the appropriate block of transformations. Each block |
38 |
/* Description of DO IF transformations: |
of transformations (except for the last) ends with a |
39 |
|
transformation that jumps past the remaining blocks. |
40 |
DO IF has two transformations. One is a conditional jump around |
|
41 |
a false condition. The second is an unconditional jump around |
So, the following code: |
42 |
the rest of the code after a true condition. Both of these types |
|
43 |
have their destinations backpatched in by the next clause (ELSE IF, |
DO IF a. |
44 |
END IF). |
...block 1... |
45 |
|
ELSE IF b. |
46 |
The characters `^V<>' are meant to represent arrows. |
...block 2... |
47 |
|
ELSE. |
48 |
1. DO IF |
...block 3... |
49 |
V<<<<if false |
END IF. |
50 |
V |
|
51 |
V *. Transformations executed when the condition on DO IF is true. |
is effectively translated like this: |
52 |
V |
|
53 |
V 2. GOTO>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>V |
IF a GOTO 1, IF b GOTO 2, ELSE GOTO 3. |
54 |
V V |
1: ...block 1... |
55 |
>>1. ELSE IF V |
GOTO 4 |
56 |
V<<<<if false V |
2: ...block 2... |
57 |
V V |
GOTO 4 |
58 |
V *. Transformations executed when condition on 1st ELSE IF is true. V |
3: ...block 3... |
59 |
V V |
4: |
|
V 2. GOTO>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>V |
|
|
V V |
|
|
>>1. ELSE IF V |
|
|
V<<<<if false V |
|
|
V V |
|
|
V *. Transformations executed when condition on 2nd ELSE IF is true. V |
|
|
V V |
|
|
V 2. GOTO>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>V |
|
|
V V |
|
|
>>*. Transformations executed when no condition is true. (ELSE) V |
|
|
V |
|
|
*. Transformations after DO IF structure.<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
|
60 |
|
|
61 |
*/ |
*/ |
|
/* *INDENT-ON* */ |
|
62 |
|
|
63 |
static struct do_if_trns *parse_do_if (void); |
/* A conditional clause. */ |
64 |
static void add_ELSE_IF (struct do_if_trns *); |
struct clause |
65 |
static trns_proc_func goto_trns_proc, do_if_trns_proc; |
{ |
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; |
static trns_free_func do_if_trns_free; |
91 |
|
|
92 |
/* Parse DO IF. */ |
/* Parse DO IF. */ |
93 |
int |
int |
94 |
cmd_do_if (void) |
cmd_do_if (void) |
95 |
{ |
{ |
96 |
struct do_if_trns *t; |
struct do_if_trns *do_if = xmalloc (sizeof *do_if); |
97 |
|
do_if->clauses = NULL; |
98 |
/* Parse the transformation. */ |
do_if->clause_cnt = 0; |
|
t = parse_do_if (); |
|
|
if (!t) |
|
|
return CMD_FAILURE; |
|
99 |
|
|
100 |
/* Finish up the transformation, add to control stack, add to |
ctl_stack_push (&do_if_class, do_if); |
101 |
transformation list. */ |
add_transformation (do_if_trns_proc, do_if_trns_free, do_if); |
|
t->brk = NULL; |
|
|
t->ctl.type = CST_DO_IF; |
|
|
t->ctl.down = ctl_stack; |
|
|
t->ctl.trns = (struct trns_header *) t; |
|
|
t->ctl.brk = NULL; |
|
|
t->has_else = 0; |
|
|
ctl_stack = &t->ctl; |
|
|
add_transformation ((struct trns_header *) t); |
|
102 |
|
|
103 |
return CMD_SUCCESS; |
return parse_clause (do_if); |
104 |
} |
} |
105 |
|
|
106 |
/* Parse ELSE IF. */ |
/* Parse ELSE IF. */ |
107 |
int |
int |
108 |
cmd_else_if (void) |
cmd_else_if (void) |
109 |
{ |
{ |
110 |
/* Transformation created. */ |
struct do_if_trns *do_if = ctl_stack_top (&do_if_class); |
111 |
struct do_if_trns *t; |
if (do_if == NULL || !must_not_have_else (do_if)) |
|
|
|
|
/* Check that we're in a pleasing situation. */ |
|
|
if (!ctl_stack || ctl_stack->type != CST_DO_IF) |
|
|
{ |
|
|
msg (SE, _("There is no DO IF to match with this ELSE IF.")); |
|
|
return CMD_FAILURE; |
|
|
} |
|
|
if (((struct do_if_trns *) ctl_stack->trns)->has_else) |
|
|
{ |
|
|
msg (SE, _("The ELSE command must follow all ELSE IF commands " |
|
|
"in a DO IF structure.")); |
|
|
return CMD_FAILURE; |
|
|
} |
|
|
|
|
|
/* Parse the transformation. */ |
|
|
t = parse_do_if (); |
|
|
if (!t) |
|
112 |
return CMD_FAILURE; |
return CMD_FAILURE; |
113 |
|
return parse_clause (do_if); |
|
/* Stick in the breakout transformation. */ |
|
|
t->brk = xmalloc (sizeof *t->brk); |
|
|
t->brk->h.proc = goto_trns_proc; |
|
|
t->brk->h.free = NULL; |
|
|
|
|
|
/* Add to list of transformations, add to string of ELSE IFs. */ |
|
|
add_transformation ((struct trns_header *) t->brk); |
|
|
add_transformation ((struct trns_header *) t); |
|
|
|
|
|
add_ELSE_IF (t); |
|
|
|
|
|
if (token != '.') |
|
|
{ |
|
|
msg (SE, _("End of command expected.")); |
|
|
return CMD_TRAILING_GARBAGE; |
|
|
} |
|
|
|
|
|
return CMD_SUCCESS; |
|
114 |
} |
} |
115 |
|
|
116 |
/* Parse ELSE. */ |
/* Parse ELSE. */ |
117 |
int |
int |
118 |
cmd_else (void) |
cmd_else (void) |
119 |
{ |
{ |
120 |
struct do_if_trns *t; |
struct do_if_trns *do_if = ctl_stack_top (&do_if_class); |
121 |
|
if (do_if == NULL || !must_not_have_else (do_if)) |
122 |
/* Check that we're in a pleasing situation. */ |
return CMD_FAILURE; |
123 |
if (!ctl_stack || ctl_stack->type != CST_DO_IF) |
add_else (do_if); |
|
{ |
|
|
msg (SE, _("There is no DO IF to match with this ELSE.")); |
|
|
return CMD_FAILURE; |
|
|
} |
|
|
|
|
|
if (((struct do_if_trns *) ctl_stack->trns)->has_else) |
|
|
{ |
|
|
msg (SE, _("There may be at most one ELSE clause in each DO IF " |
|
|
"structure. It must be the last clause.")); |
|
|
return CMD_FAILURE; |
|
|
} |
|
|
|
|
|
/* Note that the ELSE transformation is *not* added to the list of |
|
|
transformations. That's because it doesn't need to do anything. |
|
|
Its goto transformation *is* added, because that's necessary. |
|
|
The main DO IF do_if_trns is the destructor for this ELSE |
|
|
do_if_trns. */ |
|
|
t = xmalloc (sizeof *t); |
|
|
t->next = NULL; |
|
|
t->brk = xmalloc (sizeof *t->brk); |
|
|
t->brk->h.proc = goto_trns_proc; |
|
|
t->brk->h.free = NULL; |
|
|
t->cond = NULL; |
|
|
add_transformation ((struct trns_header *) t->brk); |
|
|
t->h.index = t->brk->h.index + 1; |
|
|
|
|
|
/* Add to string of ELSE IFs. */ |
|
|
add_ELSE_IF (t); |
|
|
|
|
124 |
return lex_end_of_command (); |
return lex_end_of_command (); |
125 |
} |
} |
126 |
|
|
128 |
int |
int |
129 |
cmd_end_if (void) |
cmd_end_if (void) |
130 |
{ |
{ |
131 |
/* List iterator. */ |
struct do_if_trns *do_if = ctl_stack_top (&do_if_class); |
132 |
struct do_if_trns *iter; |
if (do_if == NULL) |
133 |
|
return CMD_FAILURE; |
|
/* Check that we're in a pleasing situation. */ |
|
|
if (!ctl_stack || ctl_stack->type != CST_DO_IF) |
|
|
{ |
|
|
msg (SE, _("There is no DO IF to match with this END IF.")); |
|
|
return CMD_FAILURE; |
|
|
} |
|
|
|
|
|
/* Chain down the list, backpatching destinations for gotos. */ |
|
|
iter = (struct do_if_trns *) ctl_stack->trns; |
|
|
for (;;) |
|
|
{ |
|
|
if (iter->brk) |
|
|
iter->brk->dest = n_trns; |
|
|
iter->missing_jump = n_trns; |
|
|
if (iter->next) |
|
|
iter = iter->next; |
|
|
else |
|
|
break; |
|
|
} |
|
|
iter->false_jump = n_trns; |
|
134 |
|
|
135 |
/* Pop control stack. */ |
ctl_stack_pop (do_if); |
|
ctl_stack = ctl_stack->down; |
|
136 |
|
|
137 |
return lex_end_of_command (); |
return lex_end_of_command (); |
138 |
} |
} |
139 |
|
|
140 |
/* Adds an ELSE IF or ELSE to the chain of them that hangs off the |
/* Closes out DO_IF, by adding a sentinel ELSE clause if |
141 |
main DO IF. */ |
necessary and setting past_END_IF_index. */ |
142 |
static void |
static void |
143 |
add_ELSE_IF (struct do_if_trns * t) |
close_do_if (void *do_if_) |
144 |
{ |
{ |
145 |
/* List iterator. */ |
struct do_if_trns *do_if = do_if_; |
146 |
struct do_if_trns *iter; |
|
147 |
|
if (!has_else (do_if)) |
148 |
iter = (struct do_if_trns *) ctl_stack->trns; |
add_else (do_if); |
149 |
while (iter->next) |
do_if->past_END_IF_index = next_transformation (); |
|
iter = iter->next; |
|
|
assert (iter != NULL); |
|
|
|
|
|
iter->next = t; |
|
|
iter->false_jump = t->h.index; |
|
150 |
} |
} |
151 |
|
|
152 |
/* Parses a DO IF or ELSE IF command and returns a pointer to a mostly |
/* Adds an ELSE clause to DO_IF pointing to the next |
153 |
filled in transformation. */ |
transformation. */ |
154 |
static struct do_if_trns * |
static void |
155 |
parse_do_if (void) |
add_else (struct do_if_trns *do_if) |
156 |
{ |
{ |
157 |
struct do_if_trns *t; |
assert (!has_else (do_if)); |
158 |
struct expression *e; |
add_clause (do_if, NULL, next_transformation ()); |
159 |
|
} |
160 |
|
|
161 |
e = expr_parse (default_dict, EXPR_BOOLEAN); |
/* Returns true if DO_IF does not yet have an ELSE clause. |
162 |
if (!e) |
Reports an error and returns false if it does already. */ |
163 |
return NULL; |
static bool |
164 |
if (token != '.') |
must_not_have_else (struct do_if_trns *do_if) |
165 |
|
{ |
166 |
|
if (has_else (do_if)) |
167 |
{ |
{ |
168 |
expr_free (e); |
msg (SE, _("This command may not follow ELSE in DO IF...END IF.")); |
169 |
lex_error (_("expecting end of command")); |
return false; |
|
return NULL; |
|
170 |
} |
} |
171 |
|
else |
172 |
|
return true; |
173 |
|
} |
174 |
|
|
175 |
t = xmalloc (sizeof *t); |
/* Returns true if DO_IF already has an ELSE clause, |
176 |
t->h.proc = do_if_trns_proc; |
false otherwise. */ |
177 |
t->h.free = do_if_trns_free; |
static bool |
178 |
t->next = NULL; |
has_else (struct do_if_trns *do_if) |
179 |
t->cond = e; |
{ |
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_FAILURE; |
195 |
|
|
196 |
|
add_clause (do_if, condition, next_transformation ()); |
197 |
|
|
198 |
return t; |
return lex_end_of_command (); |
199 |
} |
} |
200 |
|
|
201 |
/* Executes a goto transformation. */ |
/* Adds a clause to DO_IF that tests for the given CONDITION and, |
202 |
static int |
if true, jumps to TARGET_INDEX. */ |
203 |
goto_trns_proc (struct trns_header * t, struct ccase * c UNUSED, |
static void |
204 |
int case_num UNUSED) |
add_clause (struct do_if_trns *do_if, |
205 |
|
struct expression *condition, int target_index) |
206 |
{ |
{ |
207 |
return ((struct goto_trns *) t)->dest; |
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 |
static int |
223 |
do_if_trns_proc (struct trns_header * trns, struct ccase * c, |
do_if_trns_proc (void *do_if_, struct ccase *c, int case_num UNUSED) |
|
int case_num UNUSED) |
|
224 |
{ |
{ |
225 |
struct do_if_trns *t = (struct do_if_trns *) trns; |
struct do_if_trns *do_if = do_if_; |
226 |
double boolean; |
struct clause *clause; |
227 |
|
|
228 |
boolean = expr_evaluate_num (t->cond, c, case_num); |
for (clause = do_if->clauses; clause < do_if->clauses + do_if->clause_cnt; |
229 |
if (boolean == 1.0) |
clause++) |
|
{ |
|
|
debug_printf ((_("DO IF %d: true\n"), t->h.index)); |
|
|
return -1; |
|
|
} |
|
|
else if (boolean == 0.0) |
|
|
{ |
|
|
debug_printf ((_("DO IF %d: false\n"), t->h.index)); |
|
|
return t->false_jump; |
|
|
} |
|
|
else |
|
230 |
{ |
{ |
231 |
debug_printf ((_("DO IF %d: missing\n"), t->h.index)); |
if (clause->condition != NULL) |
232 |
return t->missing_jump; |
{ |
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 void |
static void |
247 |
do_if_trns_free (struct trns_header * trns) |
do_if_trns_free (void *do_if_) |
248 |
{ |
{ |
249 |
struct do_if_trns *t = (struct do_if_trns *) trns; |
struct do_if_trns *do_if = do_if_; |
250 |
expr_free (t->cond); |
struct clause *clause; |
251 |
|
|
252 |
/* If brk is NULL then this is the main DO IF; therefore we |
for (clause = do_if->clauses; clause < do_if->clauses + do_if->clause_cnt; |
253 |
need to chain down to the ELSE and delete it. */ |
clause++) |
254 |
if (t->brk == NULL) |
expr_free (clause->condition); |
255 |
{ |
free (do_if->clauses); |
256 |
struct do_if_trns *iter = t->next; |
free (do_if); |
257 |
while (iter) |
} |
258 |
{ |
|
259 |
if (!iter->cond) |
/* Breaks out of a DO IF construct. */ |
260 |
{ |
static int |
261 |
/* This is the ELSE. */ |
break_trns_proc (void *do_if_, struct ccase *c UNUSED, int case_num UNUSED) |
262 |
free (iter); |
{ |
263 |
break; |
struct do_if_trns *do_if = do_if_; |
264 |
} |
|
265 |
iter = iter->next; |
return do_if->past_END_IF_index; |
|
} |
|
|
} |
|
266 |
} |
} |
267 |
|
|
268 |
|
/* DO IF control structure class definition. */ |
269 |
|
static struct ctl_class do_if_class = |
270 |
|
{ |
271 |
|
"DO IF", |
272 |
|
"END IF", |
273 |
|
close_do_if, |
274 |
|
}; |