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