69288e5c1497111d611558c9e9329c1074d49741
[pspp] / src / language / control / do-if.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2009-2012 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include <stdlib.h>
20
21 #include "data/dataset.h"
22 #include "data/transformations.h"
23 #include "language/command.h"
24 #include "language/data-io/inpt-pgm.h"
25 #include "language/expressions/public.h"
26 #include "language/lexer/lexer.h"
27 #include "libpspp/compiler.h"
28 #include "libpspp/message.h"
29 #include "libpspp/str.h"
30
31 #include "gl/xalloc.h"
32
33 #include "gettext.h"
34 #define _(msgid) gettext (msgid)
35
36 /* A conditional clause. */
37 struct clause
38   {
39     struct msg_location *location;
40     struct expression *condition; /* Test expression; NULL for ELSE clause. */
41     struct trns_chain xforms;
42   };
43
44 /* DO IF transformation. */
45 struct do_if_trns
46   {
47     struct clause *clauses;     /* Clauses. */
48     size_t n_clauses;           /* Number of clauses. */
49
50     const struct trns_chain *resume;
51     size_t ofs;
52   };
53
54 static const struct trns_class do_if_trns_class;
55
56 static void
57 start_clause (struct lexer *lexer, struct dataset *ds,
58               bool condition, struct do_if_trns *do_if,
59               size_t *allocated_clauses, bool *ok)
60 {
61   if (*ok && do_if->n_clauses > 0
62       && !do_if->clauses[do_if->n_clauses - 1].condition)
63     {
64       if (condition)
65         lex_ofs_error (lexer, 0, 1,
66                        _("ELSE IF is not allowed following ELSE "
67                          "within DO IF...END IF."));
68       else
69         lex_ofs_error (lexer, 0, 0,
70                        _("Only one ELSE is allowed within DO IF...END IF."));
71
72       msg_at (SN, do_if->clauses[do_if->n_clauses - 1].location,
73               _("This is the location of the previous ELSE clause."));
74
75       msg_at (SN, do_if->clauses[0].location,
76               _("This is the location of the DO IF command."));
77
78       *ok = false;
79     }
80
81   if (do_if->n_clauses >= *allocated_clauses)
82     do_if->clauses = x2nrealloc (do_if->clauses, allocated_clauses,
83                                  sizeof *do_if->clauses);
84   struct clause *clause = &do_if->clauses[do_if->n_clauses++];
85
86   *clause = (struct clause) { .location = NULL };
87   if (condition)
88     {
89       clause->condition = expr_parse_bool (lexer, ds);
90       if (!clause->condition)
91         lex_discard_rest_of_command (lexer);
92     }
93   clause->location = lex_ofs_location (lexer, 0, lex_ofs (lexer));
94
95   lex_end_of_command (lexer);
96   lex_get (lexer);
97
98   proc_push_transformations (ds);
99 }
100
101 static void
102 finish_clause (struct dataset *ds, struct do_if_trns *do_if)
103 {
104   struct clause *clause = &do_if->clauses[do_if->n_clauses - 1];
105   proc_pop_transformations (ds, &clause->xforms);
106 }
107
108 /* Parse DO IF. */
109 int
110 cmd_do_if (struct lexer *lexer, struct dataset *ds)
111 {
112   struct do_if_trns *do_if = xmalloc (sizeof *do_if);
113   *do_if = (struct do_if_trns) { .n_clauses = 0 };
114
115   size_t allocated_clauses = 0;
116   bool ok = true;
117
118   start_clause (lexer, ds, true, do_if, &allocated_clauses, &ok);
119   while (!lex_match_phrase (lexer, "END IF"))
120     {
121       if (lex_token (lexer) == T_STOP)
122         {
123           lex_error (lexer, NULL);
124           break;
125         }
126       else if (lex_match_phrase (lexer, "ELSE IF"))
127         {
128           finish_clause (ds, do_if);
129           start_clause (lexer, ds, true, do_if, &allocated_clauses, &ok);
130         }
131       else if (lex_match_id (lexer, "ELSE"))
132         {
133           finish_clause (ds, do_if);
134           start_clause (lexer, ds, false, do_if, &allocated_clauses, &ok);
135         }
136       else
137         cmd_parse_in_state (lexer, ds,
138                             (in_input_program ()
139                              ? CMD_STATE_NESTED_INPUT_PROGRAM
140                              : CMD_STATE_NESTED_DATA));
141     }
142   finish_clause (ds, do_if);
143
144   add_transformation (ds, &do_if_trns_class, do_if);
145
146   return ok ? CMD_SUCCESS : CMD_FAILURE;
147 }
148
149 int
150 cmd_inside_do_if (struct lexer *lexer, struct dataset *ds UNUSED)
151 {
152   lex_ofs_error (lexer, 0, lex_ofs (lexer) - 1,
153                  _("This command cannot appear outside DO IF...END IF."));
154   return CMD_FAILURE;
155 }
156
157 static const struct trns_chain *
158 do_if_find_clause (const struct do_if_trns *do_if,
159                    struct ccase *c, casenumber case_num)
160 {
161   for (size_t i = 0; i < do_if->n_clauses; i++)
162     {
163       const struct clause *clause = &do_if->clauses[i];
164       if (!clause->condition)
165         return &clause->xforms;
166
167       double boolean = expr_evaluate_num (clause->condition, c, case_num);
168       if (boolean != 0.0)
169         return boolean == SYSMIS ? NULL : &clause->xforms;
170     }
171   return NULL;
172 }
173
174 static enum trns_result
175 do_if_trns_proc (void *do_if_, struct ccase **c, casenumber case_num)
176 {
177   struct do_if_trns *do_if = do_if_;
178
179   const struct trns_chain *chain;
180   size_t start;
181   if (do_if->resume)
182     {
183       chain = do_if->resume;
184       start = do_if->ofs;
185       do_if->resume = NULL;
186       do_if->ofs = 0;
187     }
188   else
189     {
190       chain = do_if_find_clause (do_if, *c, case_num);
191       if (!chain)
192         return TRNS_CONTINUE;
193       start = 0;
194     }
195
196   for (size_t i = start; i < chain->n; i++)
197     {
198       const struct transformation *trns = &chain->xforms[i];
199       enum trns_result r = trns->class->execute (trns->aux, c, case_num);
200       switch (r)
201         {
202         case TRNS_CONTINUE:
203           break;
204
205         case TRNS_BREAK:
206         case TRNS_DROP_CASE:
207         case TRNS_ERROR:
208         case TRNS_END_FILE:
209           return r;
210
211         case TRNS_END_CASE:
212           do_if->resume = chain;
213           do_if->ofs = i;
214           return r;
215         }
216     }
217   return TRNS_CONTINUE;
218 }
219
220 static bool
221 do_if_trns_free (void *do_if_)
222 {
223   struct do_if_trns *do_if = do_if_;
224
225   for (size_t i = 0; i < do_if->n_clauses; i++)
226     {
227       struct clause *clause = &do_if->clauses[i];
228
229       msg_location_destroy (clause->location);
230       expr_free (clause->condition);
231
232       trns_chain_uninit (&clause->xforms);
233     }
234   free (do_if->clauses);
235   free (do_if);
236   return true;
237 }
238
239 static const struct trns_class do_if_trns_class = {
240   .name = "DO IF",
241   .execute = do_if_trns_proc,
242   .destroy = do_if_trns_free,
243 };