1 /*
2 * gretl -- Gnu Regression, Econometrics and Time-series Library
3 * Copyright (C) 2001 Allin Cottrell and Riccardo "Jack" Lucchetti
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU 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, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 /* if-then stuff - conditional execution */
21
22 #include "libgretl.h"
23 #include "cmd_private.h"
24 #include "flow_control.h"
25
26 #define IFDEBUG 0
27
28 enum {
29 SET_FALSE,
30 SET_TRUE,
31 SET_ELSE,
32 SET_ELIF,
33 SET_ENDIF,
34 IS_FALSE,
35 IS_TRUE,
36 UNINDENT,
37 GETINDENT,
38 RELAX,
39 IFRESET
40 };
41
42 enum {
43 TOK_IF = 1,
44 TOK_ELIF,
45 TOK_ELSE,
46 TOK_ENDIF
47 };
48
49 #if 0 /* not yet */
50
51 static int inline_if (const char *s)
52 {
53 int ret = 0;
54
55 if (strchr(s, ';') != NULL) {
56 int quoted = 0;
57
58 while (*s) {
59 if (*s == '"') {
60 quoted = !quoted;
61 } else if (!quoted && *s == ';') {
62 ret = 1;
63 break;
64 }
65 s++;
66 }
67 }
68
69 return ret;
70 }
71
72 #endif
73
74 /* if_eval: evaluate an "if" condition by generating a scalar
75 (integer) representing the truth or falsity of the condition.
76 The condition is expressed in the string @s. If a loop is
77 being executed currently the @ptr argument may be non-NULL:
78 this will happen if the conditional is known NOT to be
79 subject to string substitution, in which case it can be
80 "compiled" and reused.
81 */
82
if_eval(int ci,const char * s,DATASET * dset,PRN * prn,void * ptr,int * err)83 static int if_eval (int ci, const char *s, DATASET *dset,
84 PRN *prn, void *ptr, int *err)
85 {
86 GENERATOR *ifgen = NULL;
87 double val = NADBL;
88 int ret = -1;
89
90 #if IFDEBUG
91 fprintf(stderr, "if_eval: s = '%s'\n", s);
92 #endif
93
94 if (ptr != NULL) {
95 /* We're being called from a loop, with the implicit
96 request that the if-condition be "compiled" (if
97 that's not already done) and subsequently executed
98 without having to be evaluated from scratch.
99 */
100 ifgen = *(GENERATOR **) ptr;
101 if (ifgen == NULL && s != NULL) {
102 /* Generator not compiled yet: do it now. The
103 flag OPT_P indicates that we're generating
104 a "private" scalar.
105 */
106 GENERATOR **pgen = (GENERATOR **) ptr;
107
108 *pgen = ifgen = genr_compile(s, dset, GRETL_TYPE_BOOL,
109 OPT_P | OPT_N, NULL, err);
110 }
111 }
112
113 if (ifgen != NULL) {
114 val = evaluate_if_cond(ifgen, dset, prn, err);
115 } else if (s == NULL) {
116 *err = E_DATA;
117 } else {
118 *err = 0;
119 val = generate_boolean(s, dset, prn, err);
120 }
121
122 #if IFDEBUG
123 fprintf(stderr, "if_eval: generate returned %d\n", *err);
124 #endif
125
126 if (*err) {
127 gretl_errmsg_append(_("error evaluating 'if'"), 0);
128 } else if (na(val)) {
129 *err = 1;
130 gretl_errmsg_append(_("indeterminate condition for 'if'"), 0);
131 } else {
132 ret = (int) val;
133 }
134
135 if (*err && s != NULL && *s != '\0') {
136 gchar *cond = g_strdup_printf("> %s %s", gretl_command_word(ci), s);
137
138 gretl_errmsg_append(cond, 0);
139 g_free(cond);
140 }
141
142 #if IFDEBUG
143 fprintf(stderr, "if_eval: returning %d\n", ret);
144 #endif
145
146 return ret;
147 }
148
149 #if IFDEBUG
ifstr(int c)150 static const char *ifstr (int c)
151 {
152 if (c == SET_FALSE) return "SET_FALSE";
153 if (c == SET_TRUE) return "SET_TRUE";
154 if (c == SET_ELSE) return "SET_ELSE";
155 if (c == SET_ELIF) return "SET_ELIF";
156 if (c == SET_ENDIF) return "SET_ENDIF";
157 if (c == IS_FALSE) return "IS_FALSE";
158 if (c == IS_TRUE) return "IS_TRUE";
159 if (c == UNINDENT) return "UNINDENT";
160 if (c == GETINDENT) return "GETINDENT";
161 if (c == RELAX) return "RELAX";
162 if (c == IFRESET) return "RESET";
163 return "UNKNOWN";
164 }
165 #endif
166
unmatched_message(int code)167 static void unmatched_message (int code)
168 {
169 gretl_errmsg_sprintf(_("Unmatched \"%s\""),
170 (code == SET_ELSE)? "else" :
171 (code == SET_ELIF)? "elif" : "endif");
172 }
173
174 #define IF_DEPTH 1024
175
176 /* Note: the @got_T boolean array below is used to record,
177 within an "if ... endif" block, whether any true condition
178 has been encountered. This is relevant in the case of
179 blocks containing "elif" clauses (as well as, possibly,
180 an "else" clause): by reference to @got_T we can ensure
181 that at most one branch is followed. Otherwise we'd be in
182 danger of following all branches for which the "if" part of
183 an "elif" condition turns out to be true, so disregarding
184 the "else" implicit in "elif".
185
186 A simple example:
187
188 x = 2
189 if x == 1
190 print "x = 1"
191 elif x == 2
192 print "x = 2"
193 elif x < 3
194 print "x < 3"
195 endif
196
197 Use of @got_T in this case ensures that the branch with
198 condition x < 3 is not followed.
199 */
200
ifstate(int code,int val,int * err)201 static int ifstate (int code, int val, int *err)
202 {
203 static unsigned char T[IF_DEPTH];
204 static unsigned char tok[IF_DEPTH];
205 static unsigned char got_T[IF_DEPTH];
206 static unsigned short indent;
207 int i, ret = 0;
208
209 #if IFDEBUG
210 if (code != IS_FALSE) {
211 fprintf(stderr, "ifstate: code = %s\n", ifstr(code));
212 }
213 #endif
214
215 if (code == IS_FALSE || code == IS_TRUE) {
216 for (i=1; i<=indent; i++) {
217 if (T[i] == 0) {
218 ret = 1; /* blocked */
219 break;
220 }
221 }
222 return code == IS_TRUE ? !ret : ret;
223 } else if (code == RELAX) {
224 indent = 0;
225 } else if (code == IFRESET) {
226 indent = val;
227 } else if (code == GETINDENT) {
228 ret = indent;
229 } else if (code == UNINDENT) {
230 ret = --indent;
231 } else if (code == SET_FALSE || code == SET_TRUE) {
232 indent++;
233 if (indent >= IF_DEPTH) {
234 gretl_errmsg_sprintf("IF depth (%d) exceeded", IF_DEPTH);
235 *err = E_DATA;
236 } else {
237 T[indent] = got_T[indent] = (code == SET_TRUE);
238 tok[indent] = TOK_IF;
239 }
240 } else if (code == SET_ELIF || code == SET_ELSE) {
241 if (tok[indent] != TOK_IF && tok[indent] != TOK_ELIF) {
242 unmatched_message(code);
243 *err = E_PARSE;
244 } else {
245 tok[indent] = (code == SET_ELSE)? TOK_ELSE : TOK_ELIF;
246 if (T[indent]) {
247 T[indent] = 0;
248 } else if (!got_T[indent]) {
249 T[indent] = 1;
250 }
251 }
252 } else if (code == SET_ENDIF) {
253 if (tok[indent] != TOK_IF &&
254 tok[indent] != TOK_ELIF &&
255 tok[indent] != TOK_ELSE) {
256 unmatched_message(code);
257 *err = E_PARSE;
258 } else {
259 tok[indent] = TOK_ENDIF;
260 got_T[indent] = 0;
261 indent--;
262 }
263 }
264
265 #if IFDEBUG
266 fprintf(stderr, "ifstate: returning %d (indent %d, err %d)\n",
267 ret, indent, (err == NULL)? 0 : *err);
268 #endif
269
270 return ret;
271 }
272
set_if_state(int code)273 static int set_if_state (int code)
274 {
275 int err = 0;
276
277 ifstate(code, 0, &err);
278 return err;
279 }
280
get_if_state(int code)281 static int get_if_state (int code)
282 {
283 int err = 0;
284
285 return ifstate(code, 0, &err);
286 }
287
gretl_if_state_clear(void)288 void gretl_if_state_clear (void)
289 {
290 #if IFDEBUG
291 fprintf(stderr, "gretl_if_state_clear called\n");
292 #endif
293 ifstate(RELAX, 0, NULL);
294 }
295
gretl_if_state_finalize(void)296 int gretl_if_state_finalize (void)
297 {
298 int ret, err = 0;
299
300 ret = ifstate(IS_TRUE, 0, NULL);
301
302 if (!ret) {
303 ifstate(RELAX, 0, NULL);
304 err = E_PARSE;
305 }
306
307 return err;
308 }
309
gretl_if_state_record(void)310 int gretl_if_state_record (void)
311 {
312 return ifstate(GETINDENT, 0, NULL);
313 }
314
gretl_if_state_reset(int indent)315 void gretl_if_state_reset (int indent)
316 {
317 ifstate(IFRESET, indent, NULL);
318 }
319
gretl_if_state_false(void)320 int gretl_if_state_false (void)
321 {
322 return get_if_state(IS_FALSE);
323 }
324
gretl_if_state_check(int indent0)325 int gretl_if_state_check (int indent0)
326 {
327 int indent = ifstate(GETINDENT, 0, NULL);
328 int err = 0;
329
330 if (indent != indent0) {
331 gretl_errmsg_sprintf(_("Unmatched \"%s\""), "if");
332 ifstate(RELAX, 0, NULL);
333 err = E_PARSE;
334 }
335
336 return err;
337 }
338
339 /* flow_control: if the ci (command index) member of @cmd
340 is something other than one of the flow control symbols
341 IF, ELSE, ELIF or ENDIF, this function simply returns
342 1 if execution if blocked by a false IF condition or 0
343 otherwise.
344
345 If we get one of the flow control symbols we operate
346 on the program's "if state", pushing a term onto, or
347 popping a term off, the existing stack. And in this case
348 we always return 1, which indicates to the machinery in
349 interact.c that execution of the current command is
350 completed.
351
352 We need the @dset argument in case we have to evaluate a
353 new IF condition.
354 */
355
flow_control(ExecState * s,DATASET * dset,void * ptr)356 int flow_control (ExecState *s, DATASET *dset, void *ptr)
357 {
358 CMD *cmd = s->cmd;
359 int ci = cmd->ci;
360 int blocked = get_if_state(IS_FALSE);
361 int ok, err = 0;
362
363 if (ci != IF && ci != ELSE && ci != ELIF && ci != ENDIF) {
364 return blocked;
365 }
366
367 if (ci == IF) {
368 if (blocked) {
369 /* just increase the "indent" level */
370 err = set_if_state(SET_FALSE);
371 } else {
372 /* actually evaluate the condition */
373 ok = if_eval(ci, cmd->vstart, dset, s->prn, ptr, &err);
374 if (!err) {
375 err = set_if_state(ok? SET_TRUE : SET_FALSE);
376 }
377 }
378 } else if (ci == ENDIF) {
379 err = set_if_state(SET_ENDIF);
380 } else if (ci == ELIF) {
381 err = set_if_state(SET_ELIF);
382 if (!err && get_if_state(IS_TRUE)) {
383 set_if_state(UNINDENT);
384 ok = if_eval(ci, cmd->vstart, dset, s->prn, ptr, &err);
385 if (!err) {
386 err = set_if_state(ok? SET_TRUE : SET_FALSE);
387 }
388 }
389 } else if (ci == ELSE) {
390 err = set_if_state(SET_ELSE);
391 }
392
393 if (err) {
394 set_if_state(RELAX);
395 cmd->err = err;
396 }
397
398 return 1;
399 }
400