You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
- 02111-1307, USA. */
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA. */
#include <config.h>
-#include "do-ifP.h"
-#include <assert.h>
+#include "ctl-stack.h"
+#include "error.h"
#include <stdlib.h>
#include "alloc.h"
#include "command.h"
#include "error.h"
-#include "expr.h"
+#include "expressions/public.h"
#include "lexer.h"
#include "str.h"
#include "var.h"
-#include "debug-print.h"
-
-#if DEBUGGING
-#include <stdio.h>
-#endif
-
-/* *INDENT-OFF* */
-/* Description of DO IF transformations:
-
- DO IF has two transformations. One is a conditional jump around
- a false condition. The second is an unconditional jump around
- the rest of the code after a true condition. Both of these types
- have their destinations backpatched in by the next clause (ELSE IF,
- END IF).
-
- The characters `^V<>' are meant to represent arrows.
-
- 1. DO IF
- V<<<<if false
- V
- V *. Transformations executed when the condition on DO IF is true.
- V
- V 2. GOTO>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>V
- V V
- >>1. ELSE IF V
- V<<<<if false V
- V V
- V *. Transformations executed when condition on 1st ELSE IF is true. V
- V V
- V 2. GOTO>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>V
- V V
- >>1. ELSE IF V
- V<<<<if false V
- V V
- V *. Transformations executed when condition on 2nd ELSE IF is true. V
- V V
- V 2. GOTO>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>V
- V V
- >>*. Transformations executed when no condition is true. (ELSE) V
- V
- *. Transformations after DO IF structure.<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+/* DO IF, ELSE IF, and ELSE are translated as a single
+ transformation that evaluates each condition and jumps to the
+ start of the appropriate block of transformations. Each block
+ of transformations (except for the last) ends with a
+ transformation that jumps past the remaining blocks.
+
+ So, the following code:
+
+ DO IF a.
+ ...block 1...
+ ELSE IF b.
+ ...block 2...
+ ELSE.
+ ...block 3...
+ END IF.
+
+ is effectively translated like this:
+
+ IF a GOTO 1, IF b GOTO 2, ELSE GOTO 3.
+ 1: ...block 1...
+ GOTO 4
+ 2: ...block 2...
+ GOTO 4
+ 3: ...block 3...
+ 4:
*/
-/* *INDENT-ON* */
-static struct do_if_trns *parse_do_if (void);
-static void add_ELSE_IF (struct do_if_trns *);
-static int goto_trns_proc (struct trns_header *, struct ccase *);
-static int do_if_trns_proc (struct trns_header *, struct ccase *);
-static void do_if_trns_free (struct trns_header *);
+/* A conditional clause. */
+struct clause
+ {
+ struct expression *condition; /* Test expression; NULL for ELSE clause. */
+ int target_index; /* Transformation to jump to if true. */
+ };
+
+/* DO IF transformation. */
+struct do_if_trns
+ {
+ struct clause *clauses; /* Clauses. */
+ size_t clause_cnt; /* Number of clauses. */
+ int past_END_IF_index; /* Transformation just past last clause. */
+ };
+
+static struct ctl_class do_if_class;
+
+static int parse_clause (struct do_if_trns *);
+static void add_clause (struct do_if_trns *,
+ struct expression *condition, int target_index);
+static void add_else (struct do_if_trns *);
+
+static bool has_else (struct do_if_trns *);
+static bool must_not_have_else (struct do_if_trns *);
+static void close_do_if (void *do_if);
+
+static trns_proc_func do_if_trns_proc, break_trns_proc;
+static trns_free_func do_if_trns_free;
/* Parse DO IF. */
int
cmd_do_if (void)
{
- struct do_if_trns *t;
+ struct do_if_trns *do_if = xmalloc (sizeof *do_if);
+ do_if->clauses = NULL;
+ do_if->clause_cnt = 0;
- /* Parse the transformation. */
- t = parse_do_if ();
- if (!t)
- return CMD_FAILURE;
+ ctl_stack_push (&do_if_class, do_if);
+ add_transformation (do_if_trns_proc, do_if_trns_free, do_if);
- /* Finish up the transformation, add to control stack, add to
- transformation list. */
- t->brk = NULL;
- t->ctl.type = CST_DO_IF;
- t->ctl.down = ctl_stack;
- t->ctl.trns = (struct trns_header *) t;
- t->ctl.brk = NULL;
- t->has_else = 0;
- ctl_stack = &t->ctl;
- add_transformation ((struct trns_header *) t);
-
- return CMD_SUCCESS;
+ return parse_clause (do_if);
}
/* Parse ELSE IF. */
int
cmd_else_if (void)
{
- /* Transformation created. */
- struct do_if_trns *t;
-
- /* Check that we're in a pleasing situation. */
- if (!ctl_stack || ctl_stack->type != CST_DO_IF)
- {
- msg (SE, _("There is no DO IF to match with this ELSE IF."));
- return CMD_FAILURE;
- }
- if (((struct do_if_trns *) ctl_stack->trns)->has_else)
- {
- msg (SE, _("The ELSE command must follow all ELSE IF commands "
- "in a DO IF structure."));
- return CMD_FAILURE;
- }
-
- /* Parse the transformation. */
- t = parse_do_if ();
- if (!t)
+ struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
+ if (do_if == NULL || !must_not_have_else (do_if))
return CMD_FAILURE;
-
- /* Stick in the breakout transformation. */
- t->brk = xmalloc (sizeof *t->brk);
- t->brk->h.proc = goto_trns_proc;
- t->brk->h.free = NULL;
-
- /* Add to list of transformations, add to string of ELSE IFs. */
- add_transformation ((struct trns_header *) t->brk);
- add_transformation ((struct trns_header *) t);
-
- add_ELSE_IF (t);
-
- if (token != '.')
- {
- msg (SE, _("End of command expected."));
- return CMD_TRAILING_GARBAGE;
- }
-
- return CMD_SUCCESS;
+ return parse_clause (do_if);
}
/* Parse ELSE. */
int
cmd_else (void)
{
- struct do_if_trns *t;
-
- lex_match_id ("ELSE");
-
- /* Check that we're in a pleasing situation. */
- if (!ctl_stack || ctl_stack->type != CST_DO_IF)
- {
- msg (SE, _("There is no DO IF to match with this ELSE."));
- return CMD_FAILURE;
- }
-
- if (((struct do_if_trns *) ctl_stack->trns)->has_else)
- {
- msg (SE, _("There may be at most one ELSE clause in each DO IF "
- "structure. It must be the last clause."));
- return CMD_FAILURE;
- }
-
- /* Note that the ELSE transformation is *not* added to the list of
- transformations. That's because it doesn't need to do anything.
- Its goto transformation *is* added, because that's necessary.
- The main DO IF do_if_trns is the destructor for this ELSE
- do_if_trns. */
- t = xmalloc (sizeof *t);
- t->next = NULL;
- t->brk = xmalloc (sizeof *t->brk);
- t->brk->h.proc = goto_trns_proc;
- t->brk->h.free = NULL;
- t->cond = NULL;
- add_transformation ((struct trns_header *) t->brk);
- t->h.index = t->brk->h.index + 1;
-
- /* Add to string of ELSE IFs. */
- add_ELSE_IF (t);
-
+ struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
+ if (do_if == NULL || !must_not_have_else (do_if))
+ return CMD_FAILURE;
+ add_else (do_if);
return lex_end_of_command ();
}
int
cmd_end_if (void)
{
- /* List iterator. */
- struct do_if_trns *iter;
-
- lex_match_id ("IF");
-
- /* Check that we're in a pleasing situation. */
- if (!ctl_stack || ctl_stack->type != CST_DO_IF)
- {
- msg (SE, _("There is no DO IF to match with this END IF."));
- return CMD_FAILURE;
- }
-
- /* Chain down the list, backpatching destinations for gotos. */
- iter = (struct do_if_trns *) ctl_stack->trns;
- for (;;)
- {
- if (iter->brk)
- iter->brk->dest = n_trns;
- iter->missing_jump = n_trns;
- if (iter->next)
- iter = iter->next;
- else
- break;
- }
- iter->false_jump = n_trns;
+ struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
+ if (do_if == NULL)
+ return CMD_FAILURE;
- /* Pop control stack. */
- ctl_stack = ctl_stack->down;
+ ctl_stack_pop (do_if);
return lex_end_of_command ();
}
-/* Adds an ELSE IF or ELSE to the chain of them that hangs off the
- main DO IF. */
+/* Closes out DO_IF, by adding a sentinel ELSE clause if
+ necessary and setting past_END_IF_index. */
static void
-add_ELSE_IF (struct do_if_trns * t)
+close_do_if (void *do_if_)
{
- /* List iterator. */
- struct do_if_trns *iter;
+ struct do_if_trns *do_if = do_if_;
+
+ if (!has_else (do_if))
+ add_else (do_if);
+ do_if->past_END_IF_index = next_transformation ();
+}
- iter = (struct do_if_trns *) ctl_stack->trns;
- while (iter->next)
- iter = iter->next;
- assert (iter != NULL);
+/* Adds an ELSE clause to DO_IF pointing to the next
+ transformation. */
+static void
+add_else (struct do_if_trns *do_if)
+{
+ assert (!has_else (do_if));
+ add_clause (do_if, NULL, next_transformation ());
+}
- iter->next = t;
- iter->false_jump = t->h.index;
+/* Returns true if DO_IF does not yet have an ELSE clause.
+ Reports an error and returns false if it does already. */
+static bool
+must_not_have_else (struct do_if_trns *do_if)
+{
+ if (has_else (do_if))
+ {
+ msg (SE, _("This command may not follow ELSE in DO IF...END IF."));
+ return false;
+ }
+ else
+ return true;
}
-/* Parses a DO IF or ELSE IF command and returns a pointer to a mostly
- filled in transformation. */
-static struct do_if_trns *
-parse_do_if (void)
+/* Returns true if DO_IF already has an ELSE clause,
+ false otherwise. */
+static bool
+has_else (struct do_if_trns *do_if)
{
- struct do_if_trns *t;
- struct expression *e;
+ return (do_if->clause_cnt != 0
+ && do_if->clauses[do_if->clause_cnt - 1].condition == NULL);
+}
- lex_match_id ("IF");
+/* Parses a DO IF or ELSE IF expression and appends the
+ corresponding clause to DO_IF. Checks for end of command and
+ returns a command return code. */
+static int
+parse_clause (struct do_if_trns *do_if)
+{
+ struct expression *condition;
- e = expr_parse (PXP_BOOLEAN);
- if (!e)
- return NULL;
- if (token != '.')
- {
- expr_free (e);
- lex_error (_("expecting end of command"));
- return NULL;
- }
+ condition = expr_parse (default_dict, EXPR_BOOLEAN);
+ if (condition == NULL)
+ return CMD_FAILURE;
- t = xmalloc (sizeof *t);
- t->h.proc = do_if_trns_proc;
- t->h.free = do_if_trns_free;
- t->next = NULL;
- t->cond = e;
+ add_clause (do_if, condition, next_transformation ());
- return t;
+ return lex_end_of_command ();
}
-/* Executes a goto transformation. */
-static int
-goto_trns_proc (struct trns_header * t, struct ccase * c unused)
+/* Adds a clause to DO_IF that tests for the given CONDITION and,
+ if true, jumps to TARGET_INDEX. */
+static void
+add_clause (struct do_if_trns *do_if,
+ struct expression *condition, int target_index)
{
- return ((struct goto_trns *) t)->dest;
+ struct clause *clause;
+
+ if (do_if->clause_cnt > 0)
+ add_transformation (break_trns_proc, NULL, do_if);
+
+ do_if->clauses = xnrealloc (do_if->clauses,
+ do_if->clause_cnt + 1, sizeof *do_if->clauses);
+ clause = &do_if->clauses[do_if->clause_cnt++];
+ clause->condition = condition;
+ clause->target_index = target_index;
}
+/* DO IF transformation procedure.
+ Checks each clause and jumps to the appropriate
+ transformation. */
static int
-do_if_trns_proc (struct trns_header * trns, struct ccase * c)
+do_if_trns_proc (void *do_if_, struct ccase *c, int case_num UNUSED)
{
- struct do_if_trns *t = (struct do_if_trns *) trns;
- union value bool;
+ struct do_if_trns *do_if = do_if_;
+ struct clause *clause;
- expr_evaluate (t->cond, c, &bool);
- if (bool.f == 1.0)
- {
- debug_printf ((_("DO IF %d: true\n"), t->h.index));
- return -1;
- }
- else if (bool.f == 0.0)
- {
- debug_printf ((_("DO IF %d: false\n"), t->h.index));
- return t->false_jump;
- }
- else
+ for (clause = do_if->clauses; clause < do_if->clauses + do_if->clause_cnt;
+ clause++)
{
- debug_printf ((_("DO IF %d: missing\n"), t->h.index));
- return t->missing_jump;
+ if (clause->condition != NULL)
+ {
+ double boolean = expr_evaluate_num (clause->condition, c, case_num);
+ if (boolean == 1.0)
+ return clause->target_index;
+ else if (boolean == SYSMIS)
+ return do_if->past_END_IF_index;
+ }
+ else
+ return clause->target_index;
}
+ return do_if->past_END_IF_index;
}
+/* Frees a DO IF transformation. */
static void
-do_if_trns_free (struct trns_header * trns)
+do_if_trns_free (void *do_if_)
{
- struct do_if_trns *t = (struct do_if_trns *) trns;
- expr_free (t->cond);
+ struct do_if_trns *do_if = do_if_;
+ struct clause *clause;
+
+ for (clause = do_if->clauses; clause < do_if->clauses + do_if->clause_cnt;
+ clause++)
+ expr_free (clause->condition);
+ free (do_if->clauses);
+ free (do_if);
+}
- /* If brk is NULL then this is the main DO IF; therefore we
- need to chain down to the ELSE and delete it. */
- if (t->brk == NULL)
- {
- struct do_if_trns *iter = t->next;
- while (iter)
- {
- if (!iter->cond)
- {
- /* This is the ELSE. */
- free (iter);
- break;
- }
- iter = iter->next;
- }
- }
+/* Breaks out of a DO IF construct. */
+static int
+break_trns_proc (void *do_if_, struct ccase *c UNUSED, int case_num UNUSED)
+{
+ struct do_if_trns *do_if = do_if_;
+
+ return do_if->past_END_IF_index;
}
+
+/* DO IF control structure class definition. */
+static struct ctl_class do_if_class =
+ {
+ "DO IF",
+ "END IF",
+ close_do_if,
+ };