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