0dd6466a52895ea9c2fe475c4ad2dcab20d34a78
[pspp] / src / 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., 59 Temple Place - Suite 330, Boston, MA
18    02111-1307, USA. */
19
20 #include <config.h>
21 #include "do-ifP.h"
22 #include <assert.h>
23 #include <stdlib.h>
24 #include "alloc.h"
25 #include "command.h"
26 #include "error.h"
27 #include "expr.h"
28 #include "lexer.h"
29 #include "str.h"
30 #include "var.h"
31
32 #include "debug-print.h"
33
34 #if DEBUGGING
35 #include <stdio.h>
36 #endif
37
38 /* *INDENT-OFF* */
39 /* Description of DO IF transformations:
40
41    DO IF has two transformations.  One is a conditional jump around
42    a false condition.  The second is an unconditional jump around
43    the rest of the code after a true condition.  Both of these types
44    have their destinations backpatched in by the next clause (ELSE IF,
45    END IF).
46
47    The characters `^V<>' are meant to represent arrows.
48
49    1. DO IF
50  V<<<<if false
51  V
52  V *. Transformations executed when the condition on DO IF is true.
53  V
54  V 2. GOTO>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>V
55  V                                                                     V
56  >>1. ELSE IF                                                          V
57  V<<<<if false                                                         V
58  V                                                                     V
59  V *. Transformations executed when condition on 1st ELSE IF is true.  V
60  V                                                                     V
61  V 2. GOTO>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>V
62  V                                                                     V
63  >>1. ELSE IF                                                          V
64  V<<<<if false                                                         V
65  V                                                                     V
66  V *. Transformations executed when condition on 2nd ELSE IF is true.  V
67  V                                                                     V
68  V 2. GOTO>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>V
69  V                                                                     V
70  >>*. Transformations executed when no condition is true. (ELSE)       V
71                                                                        V
72    *. Transformations after DO IF structure.<<<<<<<<<<<<<<<<<<<<<<<<<<<<
73
74 */
75 /* *INDENT-ON* */
76
77 static struct do_if_trns *parse_do_if (void);
78 static void add_ELSE_IF (struct do_if_trns *);
79 static trns_proc_func goto_trns_proc, do_if_trns_proc;
80 static trns_free_func do_if_trns_free;
81
82 /* Parse DO IF. */
83 int
84 cmd_do_if (void)
85 {
86   struct do_if_trns *t;
87
88   /* Parse the transformation. */
89   t = parse_do_if ();
90   if (!t)
91     return CMD_FAILURE;
92
93   /* Finish up the transformation, add to control stack, add to
94      transformation list. */
95   t->brk = NULL;
96   t->ctl.type = CST_DO_IF;
97   t->ctl.down = ctl_stack;
98   t->ctl.trns = (struct trns_header *) t;
99   t->ctl.brk = NULL;
100   t->has_else = 0;
101   ctl_stack = &t->ctl;
102   add_transformation ((struct trns_header *) t);
103
104   return CMD_SUCCESS;
105 }
106
107 /* Parse ELSE IF. */
108 int
109 cmd_else_if (void)
110 {
111   /* Transformation created. */
112   struct do_if_trns *t;
113
114   /* Check that we're in a pleasing situation. */
115   if (!ctl_stack || ctl_stack->type != CST_DO_IF)
116     {
117       msg (SE, _("There is no DO IF to match with this ELSE IF."));
118       return CMD_FAILURE;
119     }
120   if (((struct do_if_trns *) ctl_stack->trns)->has_else)
121     {
122       msg (SE, _("The ELSE command must follow all ELSE IF commands "
123                  "in a DO IF structure."));
124       return CMD_FAILURE;
125     }
126
127   /* Parse the transformation. */
128   t = parse_do_if ();
129   if (!t)
130     return CMD_FAILURE;
131
132   /* Stick in the breakout transformation. */
133   t->brk = xmalloc (sizeof *t->brk);
134   t->brk->h.proc = goto_trns_proc;
135   t->brk->h.free = NULL;
136
137   /* Add to list of transformations, add to string of ELSE IFs. */
138   add_transformation ((struct trns_header *) t->brk);
139   add_transformation ((struct trns_header *) t);
140
141   add_ELSE_IF (t);
142
143   if (token != '.')
144     {
145       msg (SE, _("End of command expected."));
146       return CMD_TRAILING_GARBAGE;
147     }
148
149   return CMD_SUCCESS;
150 }
151
152 /* Parse ELSE. */
153 int
154 cmd_else (void)
155 {
156   struct do_if_trns *t;
157
158   /* Check that we're in a pleasing situation. */
159   if (!ctl_stack || ctl_stack->type != CST_DO_IF)
160     {
161       msg (SE, _("There is no DO IF to match with this ELSE."));
162       return CMD_FAILURE;
163     }
164   
165   if (((struct do_if_trns *) ctl_stack->trns)->has_else)
166     {
167       msg (SE, _("There may be at most one ELSE clause in each DO IF "
168                  "structure.  It must be the last clause."));
169       return CMD_FAILURE;
170     }
171
172   /* Note that the ELSE transformation is *not* added to the list of
173      transformations.  That's because it doesn't need to do anything.
174      Its goto transformation *is* added, because that's necessary.
175      The main DO IF do_if_trns is the destructor for this ELSE
176      do_if_trns. */
177   t = xmalloc (sizeof *t);
178   t->next = NULL;
179   t->brk = xmalloc (sizeof *t->brk);
180   t->brk->h.proc = goto_trns_proc;
181   t->brk->h.free = NULL;
182   t->cond = NULL;
183   add_transformation ((struct trns_header *) t->brk);
184   t->h.index = t->brk->h.index + 1;
185
186   /* Add to string of ELSE IFs. */
187   add_ELSE_IF (t);
188
189   return lex_end_of_command ();
190 }
191
192 /* Parse END IF. */
193 int
194 cmd_end_if (void)
195 {
196   /* List iterator. */
197   struct do_if_trns *iter;
198
199   /* Check that we're in a pleasing situation. */
200   if (!ctl_stack || ctl_stack->type != CST_DO_IF)
201     {
202       msg (SE, _("There is no DO IF to match with this END IF."));
203       return CMD_FAILURE;
204     }
205
206   /* Chain down the list, backpatching destinations for gotos. */
207   iter = (struct do_if_trns *) ctl_stack->trns;
208   for (;;)
209     {
210       if (iter->brk)
211         iter->brk->dest = n_trns;
212       iter->missing_jump = n_trns;
213       if (iter->next)
214         iter = iter->next;
215       else
216         break;
217     }
218   iter->false_jump = n_trns;
219
220   /* Pop control stack. */
221   ctl_stack = ctl_stack->down;
222
223   return lex_end_of_command ();
224 }
225
226 /* Adds an ELSE IF or ELSE to the chain of them that hangs off the
227    main DO IF. */
228 static void
229 add_ELSE_IF (struct do_if_trns * t)
230 {
231   /* List iterator. */
232   struct do_if_trns *iter;
233
234   iter = (struct do_if_trns *) ctl_stack->trns;
235   while (iter->next)
236     iter = iter->next;
237   assert (iter != NULL);
238
239   iter->next = t;
240   iter->false_jump = t->h.index;
241 }
242
243 /* Parses a DO IF or ELSE IF command and returns a pointer to a mostly
244    filled in transformation. */
245 static struct do_if_trns *
246 parse_do_if (void)
247 {
248   struct do_if_trns *t;
249   struct expression *e;
250
251   e = expr_parse (PXP_BOOLEAN);
252   if (!e)
253     return NULL;
254   if (token != '.')
255     {
256       expr_free (e);
257       lex_error (_("expecting end of command"));
258       return NULL;
259     }
260
261   t = xmalloc (sizeof *t);
262   t->h.proc = do_if_trns_proc;
263   t->h.free = do_if_trns_free;
264   t->next = NULL;
265   t->cond = e;
266
267   return t;
268 }
269
270 /* Executes a goto transformation. */
271 static int 
272 goto_trns_proc (struct trns_header * t, struct ccase * c UNUSED,
273                 int case_num UNUSED)
274 {
275   return ((struct goto_trns *) t)->dest;
276 }
277
278 static int 
279 do_if_trns_proc (struct trns_header * trns, struct ccase * c,
280                  int case_num UNUSED)
281 {
282   struct do_if_trns *t = (struct do_if_trns *) trns;
283   union value bool;
284
285   expr_evaluate (t->cond, c, case_num, &bool);
286   if (bool.f == 1.0)
287     {
288       debug_printf ((_("DO IF %d: true\n"), t->h.index));
289       return -1;
290     }
291   else if (bool.f == 0.0)
292     {
293       debug_printf ((_("DO IF %d: false\n"), t->h.index));
294       return t->false_jump;
295     }
296   else
297     {
298       debug_printf ((_("DO IF %d: missing\n"), t->h.index));
299       return t->missing_jump;
300     }
301 }
302
303 static void 
304 do_if_trns_free (struct trns_header * trns)
305 {
306   struct do_if_trns *t = (struct do_if_trns *) trns;
307   expr_free (t->cond);
308
309   /* If brk is NULL then this is the main DO IF; therefore we
310      need to chain down to the ELSE and delete it. */
311   if (t->brk == NULL)
312     {
313       struct do_if_trns *iter = t->next;
314       while (iter)
315         {
316           if (!iter->cond)
317             {
318               /* This is the ELSE. */
319               free (iter);
320               break;
321             }
322           iter = iter->next;
323         }
324     }
325 }