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