Clean up how transformations work.
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 10 Dec 2021 06:23:59 +0000 (22:23 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 11 Dec 2021 06:24:06 +0000 (22:24 -0800)
Transformations had a messy system that included "finalizers" that needed
to be called before a chain could be executed and the need to keep track
of relative offsets for control flow.  This commit reworks and simplifies
the whole thing.

Also adds some INPUT PROGRAM, LOOP, and DO IF tests.

36 files changed:
doc/data-io.texi
src/data/dataset.c
src/data/dataset.h
src/data/transformations.c
src/data/transformations.h
src/language/command.c
src/language/command.def
src/language/command.h
src/language/control/automake.mk
src/language/control/control-stack.c [deleted file]
src/language/control/control-stack.h [deleted file]
src/language/control/do-if.c
src/language/control/loop.c
src/language/control/temporary.c
src/language/data-io/data-list.c
src/language/data-io/inpt-pgm.c
src/language/data-io/inpt-pgm.h
src/language/data-io/print-space.c
src/language/data-io/print.c
src/language/data-io/save.c
src/language/expressions/parse.c
src/language/expressions/public.h
src/language/stats/autorecode.c
src/language/stats/descriptives.c
src/language/stats/quick-cluster.c
src/language/stats/rank.c
src/language/stats/regression.c
src/language/xforms/compute.c
src/language/xforms/count.c
src/language/xforms/fail.c
src/language/xforms/recode.c
src/language/xforms/sample.c
src/language/xforms/select-if.c
tests/language/control/do-if.at
tests/language/control/loop.at
tests/language/data-io/inpt-pgm.at

index 738854d16cb28c1ea75ad2daceab5a92e46e9880..9f5b8dfbeec3303c19ffe29b89249d96e8c2a67f 100644 (file)
@@ -810,118 +810,115 @@ structure.
 @cmd{INPUT PROGRAM} must contain at least one @cmd{DATA LIST} or
 @cmd{END FILE} command.
 
-All this is very confusing.  A few examples should help to clarify.
+@subheading Example 1: Read two files in parallel to the end of the shorter
+
+The following example reads variable X from file @file{a.txt} and
+variable Y from file @file{b.txt}.  If one file is shorter than the
+other then the extra data in the longer file is ignored.
 
-@c If you change this example, change the regression test1 in
-@c tests/command/input-program.sh to match.
 @example
 INPUT PROGRAM.
-        DATA LIST NOTABLE FILE='a.data'/X 1-10.
-        DATA LIST NOTABLE FILE='b.data'/Y 1-10.
+    DATA LIST NOTABLE FILE='a.txt'/X 1-10.
+    DATA LIST NOTABLE FILE='b.txt'/Y 1-10.
 END INPUT PROGRAM.
 LIST.
 @end example
 
-The example above reads variable X from file @file{a.data} and variable
-Y from file @file{b.data}.  If one file is shorter than the other then
-the extra data in the longer file is ignored.
+@subheading Example 2: Read two files in parallel, supplementing the shorter
+
+The following example also reads variable X from @file{a.txt} and
+variable Y from @file{b.txt}.  If one file is shorter than the other
+then it continues reading the longer to its end, setting the other
+variable to system-missing.
 
-@c If you change this example, change the regression test2 in
-@c tests/command/input-program.sh to match.
 @example
 INPUT PROGRAM.
-        NUMERIC #A #B.
-
-        DO IF NOT #A.
-                DATA LIST NOTABLE END=#A FILE='a.data'/X 1-10.
-        END IF.
-        DO IF NOT #B.
-                DATA LIST NOTABLE END=#B FILE='b.data'/Y 1-10.
-        END IF.
-        DO IF #A AND #B.
-                END FILE.
-        END IF.
-        END CASE.
+    NUMERIC #A #B.
+
+    DO IF NOT #A.
+        DATA LIST NOTABLE END=#A FILE='a.txt'/X 1-10.
+    END IF.
+    DO IF NOT #B.
+        DATA LIST NOTABLE END=#B FILE='b.txt'/Y 1-10.
+    END IF.
+    DO IF #A AND #B.
+        END FILE.
+    END IF.
+    END CASE.
 END INPUT PROGRAM.
 LIST.
 @end example
 
-The above example reads variable X from @file{a.data} and variable Y from
-@file{b.data}.  If one file is shorter than the other then the missing
-field is set to the system-missing value alongside the present value for
-the remaining length of the longer file.
+@subheading Example 3: Concatenate two files (version 1)
+
+The following example reads data from file @file{a.txt}, then from
+@file{b.txt}, and concatenates them into a single active dataset.
 
-@c If you change this example, change the regression test3 in
-@c tests/command/input-program.sh to match.
 @example
 INPUT PROGRAM.
-        NUMERIC #A #B.
-
-        DO IF #A.
-                DATA LIST NOTABLE END=#B FILE='b.data'/X 1-10.
-                DO IF #B.
-                        END FILE.
-                ELSE.
-                        END CASE.
-                END IF.
+    NUMERIC #A #B.
+
+    DO IF #A.
+        DATA LIST NOTABLE END=#B FILE='b.txt'/X 1-10.
+        DO IF #B.
+            END FILE.
         ELSE.
-                DATA LIST NOTABLE END=#A FILE='a.data'/X 1-10.
-                DO IF NOT #A.
-                        END CASE.
-                END IF.
+            END CASE.
         END IF.
+    ELSE.
+        DATA LIST NOTABLE END=#A FILE='a.txt'/X 1-10.
+        DO IF NOT #A.
+            END CASE.
+        END IF.
+    END IF.
 END INPUT PROGRAM.
 LIST.
 @end example
 
-The above example reads data from file @file{a.data}, then from
-@file{b.data}, and concatenates them into a single active dataset.
+@subheading Example 4: Concatenate two files (version 2)
+
+This is another way to do the same thing as Example 3.
 
-@c If you change this example, change the regression test4 in
-@c tests/command/input-program.sh to match.
 @example
 INPUT PROGRAM.
-        NUMERIC #EOF.
-
-        LOOP IF NOT #EOF.
-                DATA LIST NOTABLE END=#EOF FILE='a.data'/X 1-10.
-                DO IF NOT #EOF.
-                        END CASE.
-                END IF.
-        END LOOP.
-
-        COMPUTE #EOF = 0.
-        LOOP IF NOT #EOF.
-                DATA LIST NOTABLE END=#EOF FILE='b.data'/X 1-10.
-                DO IF NOT #EOF.
-                        END CASE.
-                END IF.
-        END LOOP.
+    NUMERIC #EOF.
 
-        END FILE.
+    LOOP IF NOT #EOF.
+        DATA LIST NOTABLE END=#EOF FILE='a.txt'/X 1-10.
+        DO IF NOT #EOF.
+            END CASE.
+        END IF.
+    END LOOP.
+
+    COMPUTE #EOF = 0.
+    LOOP IF NOT #EOF.
+        DATA LIST NOTABLE END=#EOF FILE='b.txt'/X 1-10.
+        DO IF NOT #EOF.
+            END CASE.
+        END IF.
+    END LOOP.
+
+    END FILE.
 END INPUT PROGRAM.
 LIST.
 @end example
 
-The above example does the same thing as the previous example, in a
-different way.
+@subheading Example 5: Generate random variates
+
+The follows example creates a dataset that consists of 50 random
+variates between 0 and 10.
 
-@c If you change this example, make similar changes to the regression
-@c test5 in tests/command/input-program.sh.
 @example
 INPUT PROGRAM.
-        LOOP #I=1 TO 50.
-                COMPUTE X=UNIFORM(10).
-                END CASE.
-        END LOOP.
-        END FILE.
+    LOOP #I=1 TO 50.
+        COMPUTE X=UNIFORM(10).
+        END CASE.
+    END LOOP.
+    END FILE.
 END INPUT PROGRAM.
-LIST/FORMAT=NUMBERED.
+LIST /FORMAT=NUMBERED.
 @end example
 
-The above example causes an active dataset to be created consisting of 50
-random variates between 0 and 10.
-
 @node LIST
 @section LIST
 @vindex LIST
index 8b3332ec0d78aee2b6636e7147d251ccbf5f4c70..648e00f04ee1c094ea7a60bf8068b3f60452f0c0 100644 (file)
@@ -64,20 +64,22 @@ struct dataset {
      and are finally passed to the procedure. */
   struct casereader *source;
   struct caseinit *caseinit;
-  struct trns_chain *permanent_trns_chain;
+  struct trns_chain permanent_trns_chain;
   struct dictionary *permanent_dict;
   struct casewriter *sink;
-  struct trns_chain *temporary_trns_chain;
+  struct trns_chain temporary_trns_chain;
+  bool temporary;
   struct dictionary *dict;
 
+  /* Stack of transformation chains for DO IF and LOOP and INPUT PROGRAM. */
+  struct trns_chain *stack;
+  size_t n_stack;
+  size_t allocated_stack;
+
   /* If true, cases are discarded instead of being written to
      sink. */
   bool discard_output;
 
-  /* The transformation chain that the next transformation will be
-     added to. */
-  struct trns_chain *cur_trns_chain;
-
   /* The case map used to compact a case, if necessary;
      otherwise a null pointer. */
   struct case_map *compactor;
@@ -143,13 +145,13 @@ dataset_create_finish__ (struct dataset *ds, struct session *session)
 struct dataset *
 dataset_create (struct session *session, const char *name)
 {
-  struct dataset *ds = XZALLOC (struct dataset);
-  ds->name = xstrdup (name);
-  ds->display = DATASET_FRONT;
-  ds->dict = dict_create (get_default_encoding ());
-
-  ds->caseinit = caseinit_create ();
-
+  struct dataset *ds = XMALLOC (struct dataset);
+  *ds = (struct dataset) {
+    .name = xstrdup (name),
+    .display = DATASET_FRONT,
+    .dict = dict_create (get_default_encoding ()),
+    .caseinit = caseinit_create (),
+  };
   dataset_create_finish__ (ds, session);
 
   return ds;
@@ -170,10 +172,11 @@ dataset_clone (struct dataset *old, const char *name)
   struct dataset *new;
 
   assert (old->proc_state == PROC_COMMITTED);
-  assert (trns_chain_is_empty (old->permanent_trns_chain));
+  assert (!old->permanent_trns_chain.n);
   assert (old->permanent_dict == NULL);
   assert (old->sink == NULL);
-  assert (old->temporary_trns_chain == NULL);
+  assert (!old->temporary);
+  assert (!old->temporary_trns_chain.n);
 
   new = xzalloc (sizeof *new);
   new->name = xstrdup (name);
@@ -200,7 +203,10 @@ dataset_destroy (struct dataset *ds)
       dict_unref (ds->dict);
       dict_unref (ds->permanent_dict);
       caseinit_destroy (ds->caseinit);
-      trns_chain_destroy (ds->permanent_trns_chain);
+      trns_chain_uninit (&ds->permanent_trns_chain);
+      for (size_t i = 0; i < ds->n_stack; i++)
+        trns_chain_uninit (&ds->stack[i]);
+      free (ds->stack);
       dataset_transformations_changed__ (ds, false);
       free (ds->name);
       free (ds);
@@ -386,9 +392,8 @@ proc_execute (struct dataset *ds)
 {
   bool ok;
 
-  if ((ds->temporary_trns_chain == NULL
-       || trns_chain_is_empty (ds->temporary_trns_chain))
-      && trns_chain_is_empty (ds->permanent_trns_chain))
+  if ((!ds->temporary || !ds->temporary_trns_chain.n)
+      && !ds->permanent_trns_chain.n)
     {
       ds->n_lag = 0;
       ds->discard_output = false;
@@ -425,7 +430,6 @@ proc_open_filtering (struct dataset *ds, bool filter)
   add_case_limit_trns (ds);
   if (filter)
     add_filter_trns (ds);
-  trns_chain_finalize (ds->cur_trns_chain);
 
   /* Make permanent_dict refer to the dictionary right before
      data reaches the sink. */
@@ -509,8 +513,6 @@ proc_casereader_read (struct casereader *reader UNUSED, void *ds_)
   assert (ds->proc_state == PROC_OPEN);
   for (; ; case_unref (c))
     {
-      casenumber case_nr;
-
       assert (retval == TRNS_DROP_CASE || retval == TRNS_ERROR);
       if (retval == TRNS_ERROR)
         ds->ok = false;
@@ -525,9 +527,8 @@ proc_casereader_read (struct casereader *reader UNUSED, void *ds_)
       caseinit_init_vars (ds->caseinit, c);
 
       /* Execute permanent transformations.  */
-      case_nr = ds->cases_written + 1;
-      retval = trns_chain_execute (ds->permanent_trns_chain, TRNS_CONTINUE,
-                                   &c, case_nr);
+      casenumber case_nr = ds->cases_written + 1;
+      retval = trns_chain_execute (&ds->permanent_trns_chain, case_nr, &c);
       caseinit_update_left_vars (ds->caseinit, c);
       if (retval != TRNS_CONTINUE)
         continue;
@@ -547,10 +548,10 @@ proc_casereader_read (struct casereader *reader UNUSED, void *ds_)
                           case_map_execute (ds->compactor, case_ref (c)));
 
       /* Execute temporary transformations. */
-      if (ds->temporary_trns_chain != NULL)
+      if (ds->temporary_trns_chain.n)
         {
-          retval = trns_chain_execute (ds->temporary_trns_chain, TRNS_CONTINUE,
-                                       &c, ds->cases_written);
+          retval = trns_chain_execute (&ds->temporary_trns_chain,
+                                       ds->cases_written, &c);
           if (retval != TRNS_CONTINUE)
             continue;
         }
@@ -668,63 +669,26 @@ lagged_case (const struct dataset *ds, int n_before)
     return NULL;
 }
 \f
-/* Returns the current set of permanent transformations,
-   and clears the permanent transformations.
-   For use by INPUT PROGRAM. */
-struct trns_chain *
-proc_capture_transformations (struct dataset *ds)
-{
-  struct trns_chain *chain;
-
-  assert (ds->temporary_trns_chain == NULL);
-  chain = ds->permanent_trns_chain;
-  ds->cur_trns_chain = ds->permanent_trns_chain = trns_chain_create ();
-  dataset_transformations_changed__ (ds, false);
-
-  return chain;
-}
-
-/* Adds a transformation that processes a case with PROC and
-   frees itself with FREE to the current set of transformations.
-   The functions are passed AUX as auxiliary data. */
-void
-add_transformation (struct dataset *ds, trns_proc_func *proc, trns_free_func *free, void *aux)
-{
-  trns_chain_append (ds->cur_trns_chain, NULL, proc, free, aux);
-  dataset_transformations_changed__ (ds, true);
-}
-
-/* Adds a transformation that processes a case with PROC and
-   frees itself with FREE to the current set of transformations.
-   When parsing of the block of transformations is complete,
-   FINALIZE will be called.
-   The functions are passed AUX as auxiliary data. */
+/* Adds TRNS to the current set of transformations. */
 void
-add_transformation_with_finalizer (struct dataset *ds,
-                                  trns_finalize_func *finalize,
-                                   trns_proc_func *proc,
-                                   trns_free_func *free, void *aux)
-{
-  trns_chain_append (ds->cur_trns_chain, finalize, proc, free, aux);
+add_transformation (struct dataset *ds,
+                    const struct trns_class *class, void *aux)
+{
+  struct trns_chain *chain = (ds->n_stack > 0 ? &ds->stack[ds->n_stack - 1]
+                              : ds->temporary ? &ds->temporary_trns_chain
+                              : &ds->permanent_trns_chain);
+  struct transformation t = { .class = class, .aux = aux };
+  trns_chain_append (chain, &t);
   dataset_transformations_changed__ (ds, true);
 }
 
-/* Returns the index of the next transformation.
-   This value can be returned by a transformation procedure
-   function to indicate a "jump" to that transformation. */
-size_t
-next_transformation (const struct dataset *ds)
-{
-  return trns_chain_next (ds->cur_trns_chain);
-}
-
 /* Returns true if the next call to add_transformation() will add
    a temporary transformation, false if it will add a permanent
    transformation. */
 bool
 proc_in_temporary_transformations (const struct dataset *ds)
 {
-  return ds->temporary_trns_chain != NULL;
+  return ds->temporary;
 }
 
 /* Marks the start of temporary transformations.
@@ -739,8 +703,7 @@ proc_start_temporary_transformations (struct dataset *ds)
 
       ds->permanent_dict = dict_clone (ds->dict);
 
-      trns_chain_finalize (ds->permanent_trns_chain);
-      ds->temporary_trns_chain = ds->cur_trns_chain = trns_chain_create ();
+      ds->temporary = true;
       dataset_transformations_changed__ (ds, true);
     }
 }
@@ -758,11 +721,9 @@ proc_make_temporary_transformations_permanent (struct dataset *ds)
 {
   if (proc_in_temporary_transformations (ds))
     {
-      trns_chain_finalize (ds->temporary_trns_chain);
-      trns_chain_splice (ds->permanent_trns_chain, ds->temporary_trns_chain);
-      ds->temporary_trns_chain = NULL;
+      trns_chain_splice (&ds->permanent_trns_chain, &ds->temporary_trns_chain);
 
-      ds->cur_trns_chain = ds->permanent_trns_chain;
+      ds->temporary = false;
 
       dict_unref (ds->permanent_dict);
       ds->permanent_dict = NULL;
@@ -785,10 +746,9 @@ proc_cancel_temporary_transformations (struct dataset *ds)
       ds->dict = ds->permanent_dict;
       ds->permanent_dict = NULL;
 
-      trns_chain_destroy (ds->temporary_trns_chain);
-      ds->temporary_trns_chain = NULL;
-      dataset_transformations_changed__ (
-        ds, !trns_chain_is_empty (ds->permanent_trns_chain));
+      trns_chain_clear (&ds->temporary_trns_chain);
+
+      dataset_transformations_changed__ (ds, ds->permanent_trns_chain.n != 0);
       return true;
     }
   else
@@ -802,16 +762,34 @@ proc_cancel_all_transformations (struct dataset *ds)
 {
   bool ok;
   assert (ds->proc_state == PROC_COMMITTED);
-  ok = trns_chain_destroy (ds->permanent_trns_chain);
-  ok = trns_chain_destroy (ds->temporary_trns_chain) && ok;
-  ds->permanent_trns_chain = ds->cur_trns_chain = trns_chain_create ();
-  ds->temporary_trns_chain = NULL;
+  ok = trns_chain_clear (&ds->permanent_trns_chain);
+  ok = trns_chain_clear (&ds->temporary_trns_chain) && ok;
+  ds->temporary = false;
+  for (size_t i = 0; i < ds->n_stack; i++)
+    ok = trns_chain_uninit (&ds->stack[i]) && ok;
+  ds->n_stack = 0;
   dataset_transformations_changed__ (ds, false);
 
   return ok;
 }
 
-static int
+void
+proc_push_transformations (struct dataset *ds)
+{
+  if (ds->n_stack >= ds->allocated_stack)
+    ds->stack = x2nrealloc (ds->stack, &ds->allocated_stack,
+                            sizeof *ds->stack);
+  trns_chain_init (&ds->stack[ds->n_stack++]);
+}
+
+void
+proc_pop_transformations (struct dataset *ds, struct trns_chain *chain)
+{
+  assert (ds->n_stack > 0);
+  *chain = ds->stack[--ds->n_stack];
+}
+
+static enum trns_result
 store_case_num (void *var_, struct ccase **cc, casenumber case_num)
 {
   struct variable *var = var_;
@@ -826,20 +804,18 @@ store_case_num (void *var_, struct ccase **cc, casenumber case_num)
 struct variable *
 add_permanent_ordering_transformation (struct dataset *ds)
 {
-  struct variable *temp_var;
+  struct variable *temp_var = dict_create_var_assert (ds->dict, "$ORDER", 0);
+  struct variable *order_var
+    = (proc_in_temporary_transformations (ds)
+       ? dict_clone_var_in_place_assert (ds->permanent_dict, temp_var)
+       : temp_var);
 
-  temp_var = dict_create_var_assert (ds->dict, "$ORDER", 0);
-  if (proc_in_temporary_transformations (ds))
-    {
-      struct variable *perm_var;
-
-      perm_var = dict_clone_var_in_place_assert (ds->permanent_dict, temp_var);
-      trns_chain_append (ds->permanent_trns_chain, NULL, store_case_num,
-                         NULL, perm_var);
-      trns_chain_finalize (ds->permanent_trns_chain);
-    }
-  else
-    add_transformation (ds, store_case_num, NULL, temp_var);
+  static const struct trns_class trns_class = {
+    .name = "ordering",
+    .execute = store_case_num
+  };
+  const struct transformation t = { .class = &trns_class, .aux = order_var };
+  trns_chain_append (&ds->permanent_trns_chain, &t);
 
   return temp_var;
 }
@@ -876,28 +852,9 @@ dataset_end_of_command (struct dataset *ds)
   return true;
 }
 \f
-static trns_proc_func case_limit_trns_proc;
-static trns_free_func case_limit_trns_free;
-
-/* Adds a transformation that limits the number of cases that may
-   pass through, if DS->DICT has a case limit. */
-static void
-add_case_limit_trns (struct dataset *ds)
-{
-  casenumber case_limit = dict_get_case_limit (ds->dict);
-  if (case_limit != 0)
-    {
-      casenumber *cases_remaining = xmalloc (sizeof *cases_remaining);
-      *cases_remaining = case_limit;
-      add_transformation (ds, case_limit_trns_proc, case_limit_trns_free,
-                          cases_remaining);
-      dict_set_case_limit (ds->dict, 0);
-    }
-}
-
 /* Limits the maximum number of cases processed to
    *CASES_REMAINING. */
-static int
+static enum trns_result
 case_limit_trns_proc (void *cases_remaining_,
                       struct ccase **c UNUSED, casenumber case_nr UNUSED)
 {
@@ -919,24 +876,32 @@ case_limit_trns_free (void *cases_remaining_)
   free (cases_remaining);
   return true;
 }
-\f
-static trns_proc_func filter_trns_proc;
 
-/* Adds a temporary transformation to filter data according to
-   the variable specified on FILTER, if any. */
+/* Adds a transformation that limits the number of cases that may
+   pass through, if DS->DICT has a case limit. */
 static void
-add_filter_trns (struct dataset *ds)
+add_case_limit_trns (struct dataset *ds)
 {
-  struct variable *filter_var = dict_get_filter (ds->dict);
-  if (filter_var != NULL)
+  casenumber case_limit = dict_get_case_limit (ds->dict);
+  if (case_limit != 0)
     {
-      proc_start_temporary_transformations (ds);
-      add_transformation (ds, filter_trns_proc, NULL, filter_var);
+      casenumber *cases_remaining = xmalloc (sizeof *cases_remaining);
+      *cases_remaining = case_limit;
+
+      static const struct trns_class trns_class = {
+        .name = "case limit",
+        .execute = case_limit_trns_proc,
+        .destroy = case_limit_trns_free,
+      };
+      add_transformation (ds, &trns_class, cases_remaining);
+
+      dict_set_case_limit (ds->dict, 0);
     }
 }
 
+\f
 /* FILTER transformation. */
-static int
+static enum trns_result
 filter_trns_proc (void *filter_var_,
                   struct ccase **c, casenumber case_nr UNUSED)
 
@@ -947,6 +912,23 @@ filter_trns_proc (void *filter_var_,
           ? TRNS_CONTINUE : TRNS_DROP_CASE);
 }
 
+/* Adds a temporary transformation to filter data according to
+   the variable specified on FILTER, if any. */
+static void
+add_filter_trns (struct dataset *ds)
+{
+  struct variable *filter_var = dict_get_filter (ds->dict);
+  if (filter_var != NULL)
+    {
+      proc_start_temporary_transformations (ds);
+
+      static const struct trns_class trns_class = {
+        .name = "FILTER",
+        .execute = filter_trns_proc,
+      };
+      add_transformation (ds, &trns_class, filter_var);
+    }
+}
 
 void
 dataset_need_lag (struct dataset *ds, int n_before)
index ce8b980d2e69c1cbc6207bd7c3ce8dc3b77a3c23..1fe535536f6cee4bd5d9da065d0e4a0bb7c9dedd 100644 (file)
@@ -26,6 +26,7 @@ struct casereader;
 struct dataset;
 struct dictionary;
 struct session;
+struct transformation;
 \f
 struct dataset *dataset_create (struct session *, const char *);
 struct dataset *dataset_clone (struct dataset *, const char *);
@@ -78,16 +79,11 @@ void dataset_set_display (struct dataset *, enum dataset_display);
 \f
 /* Transformations. */
 
-void add_transformation (struct dataset *ds,
-                        trns_proc_func *, trns_free_func *, void *);
-void add_transformation_with_finalizer (struct dataset *ds,
-                                       trns_finalize_func *,
-                                        trns_proc_func *,
-                                        trns_free_func *, void *);
-size_t next_transformation (const struct dataset *ds);
+void add_transformation (struct dataset *ds, const struct trns_class *, void *);
 
 bool proc_cancel_all_transformations (struct dataset *ds);
-struct trns_chain *proc_capture_transformations (struct dataset *ds);
+void proc_push_transformations (struct dataset *);
+void proc_pop_transformations (struct dataset *, struct trns_chain *);
 
 void proc_start_temporary_transformations (struct dataset *ds);
 bool proc_in_temporary_transformations (const struct dataset *ds);
index cd5c6a188c8048319090083ab6c221cf870527fc..f1cfaf47c9688c5d51cd273d7d623b34292d3449 100644 (file)
 
 #include "gl/xalloc.h"
 
-/* A single transformation. */
-struct transformation
-  {
-    /* Offset to add to EXECUTE's return value, if it returns a
-       transformation index.  Normally 0 but set to the starting
-       index of a spliced chain after splicing. */
-    int idx_ofs;
-    trns_finalize_func *finalize;       /* Finalize proc. */
-    trns_proc_func *execute;            /* Executes the transformation. */
-    trns_free_func *free;               /* Garbage collector proc. */
-    void *aux;                          /* Auxiliary data. */
-  };
-
-/* A chain of transformations. */
-struct trns_chain
-  {
-    struct transformation *trns;        /* Array of transformations. */
-    size_t n_trns;                      /* Number of transformations. */
-    size_t allocated_trns;              /* Allocated capacity. */
-    bool finalized;                     /* Finalize functions called? */
-  };
-
-/* Allocates and returns a new transformation chain. */
-struct trns_chain *
-trns_chain_create (void)
-{
-  struct trns_chain *chain = xmalloc (sizeof *chain);
-  chain->trns = NULL;
-  chain->n_trns = 0;
-  chain->allocated_trns = 0;
-  chain->finalized = false;
-  return chain;
-}
-
-/* Finalizes all the un-finalized transformations in CHAIN.
-   Any given transformation is only finalized once. */
 void
-trns_chain_finalize (struct trns_chain *chain)
+trns_chain_init (struct trns_chain *chain)
 {
-  while (!chain->finalized)
-    {
-      size_t i;
-
-      chain->finalized = true;
-      for (i = 0; i < chain->n_trns; i++)
-        {
-          struct transformation *trns = &chain->trns[i];
-          trns_finalize_func *finalize = trns->finalize;
-
-          trns->finalize = NULL;
-          if (finalize != NULL)
-            finalize (trns->aux);
-        }
-    }
+  *chain = (struct trns_chain) TRNS_CHAIN_INIT;
 }
 
-/* Destroys CHAIN, finalizing it in the process if it has not
-   already been finalized. */
 bool
-trns_chain_destroy (struct trns_chain *chain)
+trns_chain_uninit (struct trns_chain *chain)
 {
   bool ok = true;
-
-  if (chain != NULL)
+  for (size_t i = 0; i < chain->n; i++)
     {
-      size_t i;
-
-      /* Needed to ensure that the control stack gets cleared. */
-      trns_chain_finalize (chain);
-
-      for (i = 0; i < chain->n_trns; i++)
-        {
-          struct transformation *trns = &chain->trns[i];
-          if (trns->free != NULL)
-            ok = trns->free (trns->aux) && ok;
-        }
-      free (chain->trns);
-      free (chain);
+      struct transformation *xform = &chain->xforms[i];
+      if (xform->class->destroy)
+        ok = xform->class->destroy (xform->aux) && ok;
     }
-
+  free (chain->xforms);
   return ok;
 }
 
-/* Returns true if CHAIN contains any transformations,
-   false otherwise. */
 bool
-trns_chain_is_empty (const struct trns_chain *chain)
+trns_chain_clear (struct trns_chain *chain)
 {
-  return chain->n_trns == 0;
+  bool ok = trns_chain_uninit (chain);
+  trns_chain_init (chain);
+  return ok;
 }
 
-/* Adds a transformation to CHAIN with finalize function
-   FINALIZE, execute function EXECUTE, free function FREE, and
-   auxiliary data AUX. */
 void
-trns_chain_append (struct trns_chain *chain, trns_finalize_func *finalize,
-                   trns_proc_func *execute, trns_free_func *free,
-                   void *aux)
+trns_chain_append (struct trns_chain *chain, const struct transformation *t)
 {
-  struct transformation *trns;
-
-  chain->finalized = false;
-
-  if (chain->n_trns == chain->allocated_trns)
-    chain->trns = x2nrealloc (chain->trns, &chain->allocated_trns,
-                              sizeof *chain->trns);
+  if (chain->n >= chain->allocated)
+    chain->xforms = x2nrealloc (chain->xforms, &chain->allocated,
+                                sizeof *chain->xforms);
 
-  trns = &chain->trns[chain->n_trns++];
-  trns->idx_ofs = 0;
-  trns->finalize = finalize;
-  trns->execute = execute;
-  trns->free = free;
-  trns->aux = aux;
+  chain->xforms[chain->n++] = *t;
 }
 
-/* Appends the transformations in SRC to those in DST,
-   and destroys SRC.
-   Both DST and SRC must already be finalized. */
 void
 trns_chain_splice (struct trns_chain *dst, struct trns_chain *src)
 {
-  size_t i;
-
-  assert (dst->finalized);
-  assert (src->finalized);
-
-  if (dst->n_trns + src->n_trns > dst->allocated_trns)
-    {
-      dst->allocated_trns = dst->n_trns + src->n_trns;
-      dst->trns = xnrealloc (dst->trns, dst->allocated_trns, sizeof *dst->trns);
-    }
-
-  for (i = 0; i < src->n_trns; i++)
+  if (dst->n + src->n >= dst->allocated)
     {
-      struct transformation *d = &dst->trns[i + dst->n_trns];
-      const struct transformation *s = &src->trns[i];
-      *d = *s;
-      d->idx_ofs += src->n_trns;
+      dst->allocated = dst->n + src->n;
+      dst->xforms = xrealloc (dst->xforms,
+                              dst->allocated * sizeof *dst->xforms);
     }
-  dst->n_trns += src->n_trns;
 
-  src->n_trns = 0;
-  trns_chain_destroy (src);
+  memcpy (&dst->xforms[dst->n], src->xforms, src->n * sizeof *src->xforms);
+  dst->n += src->n;
+  src->n = 0;
 }
 
-/* Returns the index that a transformation execution function may
-   return to "jump" to the next transformation to be added. */
-size_t
-trns_chain_next (struct trns_chain *chain)
-{
-  return chain->n_trns;
-}
-
-/* Executes the given CHAIN of transformations on *C,
-   passing CASE_NR as the case number.
-   *C may be replaced by a new case.
-   Returns the result code that caused the transformations to
-   terminate, or TRNS_CONTINUE if the transformations finished
-   due to "falling off the end" of the set of transformations. */
+/* Executes the N transformations in XFORMS against case *C passing CASE_NR as
+   the case number.  The transformations may replace *C by a new case.  Returns
+   the result code that caused the transformations to terminate, or
+   TRNS_CONTINUE if the transformations finished due to "falling off the end"
+   of the set of transformations. */
 enum trns_result
-trns_chain_execute (const struct trns_chain *chain, enum trns_result start,
-                    struct ccase **c, casenumber case_nr)
+trns_chain_execute (const struct trns_chain *chain,
+                    casenumber case_nr, struct ccase **c)
 {
-  size_t i;
-
-  assert (chain->finalized);
-  for (i = start < 0 ? 0 : start; i < chain->n_trns;)
+  for (size_t i = 0; i < chain->n; i++)
     {
-      struct transformation *trns = &chain->trns[i];
-      int retval = trns->execute (trns->aux, c, case_nr);
-      if (retval == TRNS_CONTINUE)
-        i++;
-      else if (retval >= 0)
-        i = retval + trns->idx_ofs;
-      else
-        return retval == TRNS_END_CASE ? i + 1 : retval;
+      const struct transformation *trns = &chain->xforms[i];
+      int retval = trns->class->execute (trns->aux, c, case_nr);
+      if (retval != TRNS_CONTINUE)
+        return retval;
     }
 
   return TRNS_CONTINUE;
index 013bcaa731e8e29adedaad8eb04d75b46e3e37e4..dd8c29d0c873c2b048d9953c47b3e2d3d86a9212 100644 (file)
 #include <stddef.h>
 #include "data/case.h"
 
-/* trns_proc_func return values. */
+/* One transformation. */
+
 enum trns_result
   {
-    TRNS_CONTINUE = -1,         /* Continue to next transformation. */
-    TRNS_DROP_CASE = -2,        /* Drop this case. */
-    TRNS_ERROR = -3,            /* A serious error, so stop the procedure. */
-    TRNS_END_CASE = -4,         /* Skip to next case.  INPUT PROGRAM only. */
-    TRNS_END_FILE = -5          /* End of input.  INPUT PROGRAM only. */
+    TRNS_CONTINUE,              /* Continue to next transformation. */
+    TRNS_BREAK,                 /* Break out of LOOP. */
+    TRNS_DROP_CASE,             /* Drop this case. */
+    TRNS_ERROR,                 /* A serious error, so stop the procedure. */
+    TRNS_END_CASE,              /* Skip to next case.  INPUT PROGRAM only. */
+    TRNS_END_FILE               /* End of input.  INPUT PROGRAM only. */
   };
 
 struct ccase;
-typedef void trns_finalize_func (void *);
-typedef int trns_proc_func (void *, struct ccase **, casenumber);
-typedef bool trns_free_func (void *);
+
+struct trns_class
+  {
+    const char *name;           /* For debugging. */
+    enum trns_result (*execute) (void *aux, struct ccase **, casenumber);
+    bool (*destroy) (void *aux);
+  };
+
+struct transformation
+  {
+    const struct trns_class *class;
+    void *aux;
+  };
 \f
-/* Transformation chains. */
+/* A chain of transformations. */
 
-struct trns_chain *trns_chain_create (void);
-void trns_chain_finalize (struct trns_chain *);
-bool trns_chain_destroy (struct trns_chain *);
+struct trns_chain
+  {
+    struct transformation *xforms;
+    size_t n;
+    size_t allocated;
+  };
 
-bool trns_chain_is_empty (const struct trns_chain *);
+#define TRNS_CHAIN_INIT { .n = 0 }
 
-void trns_chain_append (struct trns_chain *, trns_finalize_func *,
-                        trns_proc_func *, trns_free_func *, void *);
-size_t trns_chain_next (struct trns_chain *);
-enum trns_result trns_chain_execute (const struct trns_chain *,
-                                     enum trns_result, struct ccase **,
-                                     casenumber case_nr);
+void trns_chain_init (struct trns_chain *);
+bool trns_chain_uninit (struct trns_chain *);
 
+bool trns_chain_clear (struct trns_chain *);
+
+void trns_chain_append (struct trns_chain *, const struct transformation *);
 void trns_chain_splice (struct trns_chain *, struct trns_chain *);
 
+enum trns_result trns_chain_execute (const struct trns_chain *,
+                                     casenumber case_num, struct ccase **);
+
 #endif /* transformations.h */
index 924b221b22d2e3851dadc578bec58e64710fc284..bffd13ebb0d74b119de6d296b7e4f4fe3ba91f60 100644 (file)
@@ -55,9 +55,6 @@ cmd_result_is_valid (enum cmd_result result)
     case CMD_SUCCESS:
     case CMD_EOF:
     case CMD_FINISH:
-    case CMD_DATA_LIST:
-    case CMD_END_CASE:
-    case CMD_END_FILE:
     case CMD_FAILURE:
     case CMD_NOT_IMPLEMENTED:
     case CMD_CASCADING_FAILURE:
@@ -89,19 +86,23 @@ cmd_result_is_failure (enum cmd_result result)
 /* Command processing states. */
 enum states
   {
-    S_INITIAL = 0x01,         /* Allowed before active dataset defined. */
-    S_DATA = 0x02,            /* Allowed after active dataset defined. */
-    S_INPUT_PROGRAM = 0x04,   /* Allowed in INPUT PROGRAM. */
-    S_FILE_TYPE = 0x08,       /* Allowed in FILE TYPE. */
-    S_ANY = 0x0f              /* Allowed anywhere. */
+    S_INITIAL = 1 << CMD_STATE_INITIAL,
+    S_DATA = 1 << CMD_STATE_DATA,
+    S_INPUT_PROGRAM = 1 << CMD_STATE_INPUT_PROGRAM,
+    S_FILE_TYPE = 1 << CMD_STATE_FILE_TYPE,
+    S_NESTED_DATA = 1 << CMD_STATE_NESTED_DATA,
+    S_NESTED_INPUT_PROGRAM = 1 << CMD_STATE_NESTED_INPUT_PROGRAM,
+
+    S_NESTED_ANY = S_NESTED_DATA | S_NESTED_INPUT_PROGRAM,
+    S_ANY = S_INITIAL | S_DATA | S_INPUT_PROGRAM | S_FILE_TYPE | S_NESTED_ANY,
   };
 
 /* Other command requirements. */
 enum flags
   {
-    F_ENHANCED = 0x10,        /* Allowed only in enhanced syntax mode. */
-    F_TESTING = 0x20,         /* Allowed only in testing mode. */
-    F_ABBREV = 0x80           /* Not a candidate for name completion. */
+    F_ENHANCED = 1 << 0,        /* Allowed only in enhanced syntax mode. */
+    F_TESTING = 1 << 1,         /* Allowed only in testing mode. */
+    F_ABBREV = 1 << 2           /* Not a candidate for name completion. */
   };
 
 /* A single command. */
@@ -126,7 +127,7 @@ static const struct command commands[] =
 static const size_t n_commands = sizeof commands / sizeof *commands;
 
 static bool in_correct_state (const struct command *, enum cmd_state);
-static bool report_state_mismatch (const struct command *, enum cmd_state);
+static void report_state_mismatch (const struct command *, enum cmd_state);
 static void set_completion_state (enum cmd_state);
 \f
 /* Command parser. */
@@ -360,22 +361,22 @@ parse_command_name (struct lexer *lexer, int *n_tokens)
 static bool
 in_correct_state (const struct command *command, enum cmd_state state)
 {
-  return ((state == CMD_STATE_INITIAL && command->states & S_INITIAL)
-          || (state == CMD_STATE_DATA && command->states & S_DATA)
-          || (state == CMD_STATE_INPUT_PROGRAM
-              && command->states & S_INPUT_PROGRAM)
-          || (state == CMD_STATE_FILE_TYPE && command->states & S_FILE_TYPE));
+  return command->states & (1 << state);
 }
 
 /* Emits an appropriate error message for trying to invoke
    COMMAND in STATE. */
-static bool
+static void
 report_state_mismatch (const struct command *command, enum cmd_state state)
 {
   assert (!in_correct_state (command, state));
-  if (state == CMD_STATE_INITIAL || state == CMD_STATE_DATA)
+
+  switch (state)
     {
-      switch ((int) command->states)
+    case CMD_STATE_INITIAL:
+    case CMD_STATE_DATA:
+      switch ((int) command->states
+              & (S_INITIAL | S_DATA | S_INPUT_PROGRAM | S_FILE_TYPE))
         {
           /* One allowed state. */
         case S_INITIAL:
@@ -441,14 +442,35 @@ report_state_mismatch (const struct command *command, enum cmd_state state)
         default:
           NOT_REACHED ();
         }
-    }
-  else if (state == CMD_STATE_INPUT_PROGRAM)
-    msg (SE, _("%s is not allowed inside %s."),
-        command->name, "INPUT PROGRAM");
-  else if (state == CMD_STATE_FILE_TYPE)
-    msg (SE, _("%s is not allowed inside %s."), command->name, "FILE TYPE");
+      break;
 
-  return false;
+    case CMD_STATE_INPUT_PROGRAM:
+      msg (SE, _("%s is not allowed inside %s."),
+           command->name, "INPUT PROGRAM");
+      break;
+
+    case CMD_STATE_FILE_TYPE:
+      msg (SE, _("%s is not allowed inside %s."), command->name, "FILE TYPE");
+      break;
+
+    case CMD_STATE_NESTED_DATA:
+    case CMD_STATE_NESTED_INPUT_PROGRAM:
+      switch ((int) command->states & S_NESTED_ANY)
+        {
+        case 0:
+          msg (SE, _("%s is not allowed inside DO IF or LOOP."), command->name);
+          break;
+
+        case S_NESTED_DATA:
+          msg (SE, _("In INPUT PROGRAM, "
+                     "%s is not allowed inside DO IF or LOOP."), command->name);
+          break;
+
+        default:
+          NOT_REACHED ();
+        }
+      break;
+    }
 }
 \f
 /* Command name completion. */
index 88cbaf5cbed8f305b3b071794e61129f0d00ff26..352f7c107893067ce6e3ae8ce873d211969b3e27 100644 (file)
@@ -49,7 +49,7 @@ DEF_CMD (S_ANY, 0, "TITLE", cmd_title)
 
 /* Commands that define (or replace) the active dataset. */
 DEF_CMD (S_INITIAL | S_DATA, 0, "ADD FILES", cmd_add_files)
-DEF_CMD (S_INITIAL | S_DATA | S_INPUT_PROGRAM | S_FILE_TYPE, 0, "DATA LIST", cmd_data_list)
+DEF_CMD (S_ANY, 0, "DATA LIST", cmd_data_list)
 DEF_CMD (S_INITIAL | S_DATA, 0, "GET", cmd_get)
 DEF_CMD (S_INITIAL | S_DATA, 0, "GET DATA", cmd_get_data)
 DEF_CMD (S_INITIAL | S_DATA, 0, "IMPORT", cmd_import)
@@ -64,50 +64,49 @@ DEF_CMD (S_INITIAL | S_DATA, 0, "DATASET COPY", cmd_dataset_copy)
 DEF_CMD (S_INITIAL | S_DATA, 0, "DATASET NAME", cmd_dataset_name)
 DEF_CMD (S_INITIAL | S_DATA, 0, "DATASET DISPLAY", cmd_dataset_display)
 
-/* Transformations and utilities that may appear after active
-   file definition or within INPUT PROGRAM. */
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "ADD VALUE LABELS", cmd_add_value_labels)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "ADD DOCUMENT", cmd_add_documents)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "APPLY DICTIONARY", cmd_apply_dictionary)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "BREAK", cmd_break)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "COMPUTE", cmd_compute)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "DATAFILE ATTRIBUTE", cmd_datafile_attribute)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "DISPLAY", cmd_display)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "DOCUMENT", cmd_document)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "DO IF", cmd_do_if)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "DROP DOCUMENTS", cmd_drop_documents)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "ELSE IF", cmd_else_if)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "ELSE", cmd_else)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "END IF", cmd_end_if)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "END LOOP", cmd_end_loop)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "FORMATS", cmd_formats)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "IF", cmd_if)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "LEAVE", cmd_leave)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "LOOP", cmd_loop)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "MISSING VALUES", cmd_missing_values)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "MRSETS", cmd_mrsets)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "NUMERIC", cmd_numeric)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "PRINT EJECT", cmd_print_eject)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "PRINT FORMATS", cmd_print_formats)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "PRINT SPACE", cmd_print_space)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "PRINT", cmd_print)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "RECODE", cmd_recode)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "SELECT IF", cmd_select_if)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "SPLIT FILE", cmd_split_file)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "STRING", cmd_string)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "VALUE LABELS", cmd_value_labels)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "VARIABLE ALIGNMENT", cmd_variable_alignment)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "VARIABLE ATTRIBUTE", cmd_variable_attribute)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "VARIABLE LABELS", cmd_variable_labels)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "VARIABLE LEVEL", cmd_variable_level)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "VARIABLE ROLE", cmd_variable_role)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "VARIABLE WIDTH", cmd_variable_width)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "VECTOR", cmd_vector)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "WEIGHT", cmd_weight)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "WRITE FORMATS", cmd_write_formats)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "WRITE", cmd_write)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, F_ENHANCED, "XEXPORT", cmd_xexport)
-DEF_CMD (S_DATA | S_INPUT_PROGRAM, 0, "XSAVE", cmd_xsave)
+/* Utilities that may appear after active file definition or within INPUT PROGRAM. */
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "ADD VALUE LABELS", cmd_add_value_labels)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "ADD DOCUMENT", cmd_add_documents)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "APPLY DICTIONARY", cmd_apply_dictionary)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "DATAFILE ATTRIBUTE", cmd_datafile_attribute)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "DISPLAY", cmd_display)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "DOCUMENT", cmd_document)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "DROP DOCUMENTS", cmd_drop_documents)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "FORMATS", cmd_formats)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "LEAVE", cmd_leave)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "MISSING VALUES", cmd_missing_values)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "MRSETS", cmd_mrsets)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "PRINT FORMATS", cmd_print_formats)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "SPLIT FILE", cmd_split_file)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "VALUE LABELS", cmd_value_labels)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "VARIABLE ALIGNMENT", cmd_variable_alignment)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "VARIABLE ATTRIBUTE", cmd_variable_attribute)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "VARIABLE LABELS", cmd_variable_labels)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "VARIABLE LEVEL", cmd_variable_level)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "VARIABLE ROLE", cmd_variable_role)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "VARIABLE WIDTH", cmd_variable_width)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "VECTOR", cmd_vector)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "WEIGHT", cmd_weight)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "WRITE FORMATS", cmd_write_formats)
+
+/* Transformations. */
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "BREAK", cmd_break)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "COMPUTE", cmd_compute)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "DO IF", cmd_do_if)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "IF", cmd_if)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "LOOP", cmd_loop)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "NUMERIC", cmd_numeric)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "PRINT EJECT", cmd_print_eject)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "PRINT SPACE", cmd_print_space)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "PRINT", cmd_print)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "RECODE", cmd_recode)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "STRING", cmd_string)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "WRITE", cmd_write)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, F_ENHANCED, "XEXPORT", cmd_xexport)
+DEF_CMD (S_DATA | S_INPUT_PROGRAM | S_NESTED_ANY, 0, "XSAVE", cmd_xsave)
+
+/* Restricted transformations. */
+DEF_CMD (S_DATA | S_NESTED_DATA, 0, "SELECT IF", cmd_select_if)
 
 /* Commands that may appear after active dataset definition. */
 DEF_CMD (S_DATA, 0, "AGGREGATE", cmd_aggregate)
@@ -150,11 +149,16 @@ DEF_CMD (S_DATA, 0, "T-TEST", cmd_t_test)
 DEF_CMD (S_DATA, 0, "TEMPORARY", cmd_temporary)
 DEF_CMD (S_DATA, 0, "USE", cmd_use)
 
-/* Commands valid only with INPUT PROGRAM. */
-DEF_CMD (S_INPUT_PROGRAM, 0, "END CASE", cmd_end_case)
-DEF_CMD (S_INPUT_PROGRAM, 0, "END FILE", cmd_end_file)
-DEF_CMD (S_INPUT_PROGRAM, 0, "END INPUT PROGRAM", cmd_end_input_program)
-DEF_CMD (S_INPUT_PROGRAM, 0, "REREAD", cmd_reread)
+/* Commands valid only inside INPUT PROGRAM. */
+DEF_CMD (S_INPUT_PROGRAM | S_NESTED_INPUT_PROGRAM, 0, "END CASE", cmd_end_case)
+DEF_CMD (S_INPUT_PROGRAM | S_NESTED_INPUT_PROGRAM, 0, "END FILE", cmd_end_file)
+DEF_CMD (S_INPUT_PROGRAM | S_NESTED_INPUT_PROGRAM, 0, "REREAD", cmd_reread)
+
+/* Not really commands, but part of LOOP and DO IF syntax. */
+DEF_CMD (S_ANY, 0, "ELSE IF", cmd_inside_do_if)
+DEF_CMD (S_ANY, 0, "ELSE", cmd_inside_do_if)
+DEF_CMD (S_ANY, 0, "END IF", cmd_inside_do_if)
+DEF_CMD (S_ANY, 0, "END LOOP", cmd_inside_loop)
 
 /* Commands for testing PSPP. */
 DEF_CMD (S_ANY, F_TESTING, "DEBUG EXPAND", cmd_debug_expand)
index ab133c25e7932cf60f5e4fd835353659231c21d6..ecd2a752659d663366c3788fe7c9f5d105a36dfe 100644 (file)
@@ -27,12 +27,6 @@ enum cmd_result
     CMD_EOF = 2,                /* End of input. */
     CMD_FINISH = 3,             /* FINISH was executed. */
 
-    /* Successful return values returned by specific commands to let INPUT
-       PROGRAM function properly. */
-    CMD_DATA_LIST,
-    CMD_END_CASE,
-    CMD_END_FILE,
-
     /* Various kinds of failures. */
     CMD_FAILURE = -1,           /* Not executed at all. */
     CMD_NOT_IMPLEMENTED = -2,   /* Command not implemented. */
@@ -48,7 +42,11 @@ enum cmd_state
     CMD_STATE_INITIAL,          /* No active dataset yet defined. */
     CMD_STATE_DATA,             /* Active dataset has been defined. */
     CMD_STATE_INPUT_PROGRAM,    /* Inside INPUT PROGRAM. */
-    CMD_STATE_FILE_TYPE         /* Inside FILE TYPE. */
+    CMD_STATE_FILE_TYPE,        /* Inside FILE TYPE. */
+
+    /* Inside LOOP or DO IF... */
+    CMD_STATE_NESTED_DATA,      /* ...in CMD_STATE_DATA. */
+    CMD_STATE_NESTED_INPUT_PROGRAM, /* ...in CMD_STATE_INPUT_PROGRAM. */
   };
 
 struct dataset;
index 9d09687c81e38330552f5f23c5d6f3b01385edf4..38ecf683644e50e40ed01252fe6d594fe3b4b5cf 100644 (file)
@@ -18,8 +18,6 @@
 
 
 language_control_sources = \
-       src/language/control/control-stack.c \
-       src/language/control/control-stack.h \
        src/language/control/define.c \
        src/language/control/do-if.c \
        src/language/control/loop.c \
diff --git a/src/language/control/control-stack.c b/src/language/control/control-stack.c
deleted file mode 100644 (file)
index 8d0962b..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
-PSPP - a program for statistical analysis.
-Copyright (C) 2017 Free Software Foundation, Inc.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include <config.h>
-
-#include "language/control/control-stack.h"
-
-#include <assert.h>
-#include <stdlib.h>
-
-#include "libpspp/compiler.h"
-#include "libpspp/message.h"
-
-#include "gl/xalloc.h"
-
-#include "gettext.h"
-#define _(msgid) gettext (msgid)
-
-struct ctl_struct
-  {
-    const struct ctl_class *class;    /* Class of control structure. */
-    struct ctl_struct *down;   /* Points toward the bottom of ctl_stack. */
-    void *private;              /* Private data. */
-  };
-
-static struct ctl_struct *ctl_stack;
-
-void
-ctl_stack_clear (void)
-{
-  while (ctl_stack != NULL)
-    {
-      struct ctl_struct *top = ctl_stack;
-      msg (SE, _("%s without %s."),
-           top->class->start_name, top->class->end_name);
-      ctl_stack_pop (top->private);
-    }
-}
-
-void
-ctl_stack_push (const struct ctl_class *class, void *private)
-{
-  struct ctl_struct *ctl;
-
-  assert (private != NULL);
-  ctl = xmalloc (sizeof *ctl);
-  ctl->class = class;
-  ctl->down = ctl_stack;
-  ctl->private = private;
-  ctl_stack = ctl;
-}
-
-void *
-ctl_stack_top (const struct ctl_class *class)
-{
-  struct ctl_struct *top = ctl_stack;
-  if (top != NULL && top->class == class)
-    return top->private;
-  else
-    {
-      if (ctl_stack_search (class) != NULL)
-        msg (SE, _("This command must appear inside %s...%s, "
-                   "without intermediate %s...%s."),
-             class->start_name, class->end_name,
-             top->class->start_name, top->class->end_name);
-      return NULL;
-    }
-}
-
-void *
-ctl_stack_search (const struct ctl_class *class)
-{
-  struct ctl_struct *ctl;
-
-  for (ctl = ctl_stack; ctl != NULL; ctl = ctl->down)
-    if (ctl->class == class)
-      return ctl->private;
-
-  msg (SE, _("This command cannot appear outside %s...%s."),
-       class->start_name, class->end_name);
-  return NULL;
-}
-
-void
-ctl_stack_pop (void *private)
-{
-  struct ctl_struct *top = ctl_stack;
-
-  assert (top != NULL);
-  assert (top->private == private);
-
-  top->class->close (top->private);
-  ctl_stack = top->down;
-  free (top);
-}
-
-bool
-ctl_stack_is_empty (void)
-{
-  return ctl_stack == NULL;
-}
diff --git a/src/language/control/control-stack.h b/src/language/control/control-stack.h
deleted file mode 100644 (file)
index 9c6824b..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/* PSPP - a program for statistical analysis.
-   Copyright (C) 1997-9, 2000, 2011 Free Software Foundation, Inc.
-
-   This program is free software: you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation, either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef CTL_STACK_H
-#define CTL_STACK_H 1
-
-#include <stdbool.h>
-
-/* The following #include avoids a potential problem when Gnulib substitutes
- * for close() by putting "#define close rpl_close" into <unistd.h>, by
- * ensuring that every source file that includes this one sees the #define..
- * (It would probably be better to rename the 'close' member of struct
- * ctl_class.)  */
-#include <unistd.h>
-
-struct ctl_class
-  {
-    const char *start_name;     /* e.g. LOOP. */
-    const char *end_name;       /* e.g. END LOOP. */
-    void (*close) (void *);     /* Closes the control structure. */
-  };
-
-void ctl_stack_clear (void);
-void ctl_stack_push (const struct ctl_class *, void *private);
-void *ctl_stack_top (const struct ctl_class *);
-void *ctl_stack_search (const struct ctl_class *);
-void ctl_stack_pop (void *);
-bool ctl_stack_is_empty (void);
-
-#endif /* ctl_stack.h */
index b361fba920702e4939395857481207f09bb8d71d..66c3e78e379b927275c3653afcb1f0c22258d189 100644 (file)
 
 #include <stdlib.h>
 
-#include "data/case.h"
 #include "data/dataset.h"
 #include "data/transformations.h"
-#include "data/value.h"
 #include "language/command.h"
-#include "language/control/control-stack.h"
+#include "language/data-io/inpt-pgm.h"
 #include "language/expressions/public.h"
 #include "language/lexer/lexer.h"
 #include "libpspp/compiler.h"
 #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:
-
-*/
-
 /* A conditional clause. */
 struct clause
   {
+    struct msg_location *location;
     struct expression *condition; /* Test expression; NULL for ELSE clause. */
-    int target_index;           /* Transformation to jump to if true. */
+    struct trns_chain xforms;
   };
 
 /* DO IF transformation. */
 struct do_if_trns
   {
-    struct dataset *ds;         /* The dataset */
     struct clause *clauses;     /* Clauses. */
     size_t n_clauses;           /* Number of clauses. */
-    int past_END_IF_index;      /* Transformation just past last clause. */
-  };
-
-static const struct ctl_class do_if_class;
-
-static int parse_clause (struct lexer *, struct do_if_trns *, struct dataset *ds);
-static void add_clause (struct do_if_trns *, struct expression *condition);
-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_finalize_func do_if_finalize_func;
-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 (struct lexer *lexer, struct dataset *ds)
-{
-  struct do_if_trns *do_if = xmalloc (sizeof *do_if);
-  do_if->clauses = NULL;
-  do_if->n_clauses = 0;
-  do_if->ds = ds;
-
-  ctl_stack_push (&do_if_class, do_if);
-  add_transformation_with_finalizer (ds, do_if_finalize_func,
-                                     do_if_trns_proc, do_if_trns_free, do_if);
+    const struct trns_chain *resume;
+    size_t ofs;
+  };
 
-  return parse_clause (lexer, do_if, ds);
-}
+static const struct trns_class do_if_trns_class;
 
-/* Parse ELSE IF. */
-int
-cmd_else_if (struct lexer *lexer, struct dataset *ds)
-{
-  struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
-  if (do_if == NULL || !must_not_have_else (do_if))
-    return CMD_CASCADING_FAILURE;
-  return parse_clause (lexer, do_if, ds);
-}
-
-/* Parse ELSE. */
-int
-cmd_else (struct lexer *lexer UNUSED, struct dataset *ds)
+static void
+start_clause (struct lexer *lexer, struct dataset *ds,
+              bool condition, struct do_if_trns *do_if,
+              size_t *allocated_clauses, bool *ok)
 {
-  struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
+  if (*ok && do_if->n_clauses > 0
+      && !do_if->clauses[do_if->n_clauses - 1].condition)
+    {
+      if (condition)
+        msg (SE, _("ELSE IF is not allowed following ELSE "
+                   "within DO IF...END IF."));
+      else
+        msg (SE, _("Only one ELSE is allowed within DO IF...END IF."));
 
-  if (do_if == NULL || !must_not_have_else (do_if))
-    return CMD_CASCADING_FAILURE;
+      msg_at (SN, do_if->clauses[do_if->n_clauses - 1].location,
+              _("This is the location of the previous ELSE clause."));
 
-  assert (ds == do_if->ds);
+      msg_at (SN, do_if->clauses[0].location,
+              _("This is the location of the DO IF command."));
 
-  add_else (do_if);
-  return CMD_SUCCESS;
-}
+      *ok = false;
+    }
 
-/* Parse END IF. */
-int
-cmd_end_if (struct lexer *lexer UNUSED, struct dataset *ds)
-{
-  struct do_if_trns *do_if = ctl_stack_top (&do_if_class);
+  if (do_if->n_clauses >= *allocated_clauses)
+    do_if->clauses = x2nrealloc (do_if->clauses, allocated_clauses,
+                                 sizeof *do_if->clauses);
+  struct clause *clause = &do_if->clauses[do_if->n_clauses++];
 
-  if (do_if == NULL)
-    return CMD_CASCADING_FAILURE;
+  *clause = (struct clause) { .location = NULL };
+  if (condition)
+    {
+      clause->condition = expr_parse_bool (lexer, ds);
+      if (!clause->condition)
+        lex_discard_rest_of_command (lexer);
+    }
+  clause->location = lex_ofs_location (lexer, 0, lex_ofs (lexer));
 
-  assert (ds == do_if->ds);
-  ctl_stack_pop (do_if);
+  lex_end_of_command (lexer);
+  lex_get (lexer);
 
-  return CMD_SUCCESS;
+  proc_push_transformations (ds);
 }
 
-/* Closes out DO_IF, by adding a sentinel ELSE clause if
-   necessary and setting past_END_IF_index. */
 static void
-close_do_if (void *do_if_)
+finish_clause (struct dataset *ds, struct do_if_trns *do_if)
 {
-  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 (do_if->ds);
+  struct clause *clause = &do_if->clauses[do_if->n_clauses - 1];
+  proc_pop_transformations (ds, &clause->xforms);
 }
 
-/* Adds an ELSE clause to DO_IF pointing to the next
-   transformation. */
-static void
-add_else (struct do_if_trns *do_if)
+/* Parse DO IF. */
+int
+cmd_do_if (struct lexer *lexer, struct dataset *ds)
 {
-  assert (!has_else (do_if));
-  add_clause (do_if, NULL);
-}
+  struct do_if_trns *do_if = xmalloc (sizeof *do_if);
+  *do_if = (struct do_if_trns) { .n_clauses = 0 };
 
-/* 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))
+  size_t allocated_clauses = 0;
+  bool ok = true;
+
+  start_clause (lexer, ds, true, do_if, &allocated_clauses, &ok);
+  while (!lex_match_phrase (lexer, "END IF"))
     {
-      msg (SE, _("This command may not follow %s in %s ... %s."), "ELSE", "DO IF", "END IF");
-      return false;
+      if (lex_token (lexer) == T_STOP)
+        {
+          lex_error (lexer, NULL);
+          break;
+        }
+      else if (lex_match_phrase (lexer, "ELSE IF"))
+        {
+          finish_clause (ds, do_if);
+          start_clause (lexer, ds, true, do_if, &allocated_clauses, &ok);
+        }
+      else if (lex_match_id (lexer, "ELSE"))
+        {
+          finish_clause (ds, do_if);
+          start_clause (lexer, ds, false, do_if, &allocated_clauses, &ok);
+        }
+      else
+        cmd_parse_in_state (lexer, ds,
+                            (in_input_program ()
+                             ? CMD_STATE_NESTED_INPUT_PROGRAM
+                             : CMD_STATE_NESTED_DATA));
     }
-  else
-    return true;
-}
-
-/* Returns true if DO_IF already has an ELSE clause,
-   false otherwise. */
-static bool
-has_else (struct do_if_trns *do_if)
-{
-  return (do_if->n_clauses != 0
-          && do_if->clauses[do_if->n_clauses - 1].condition == NULL);
-}
-
-/* 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 lexer *lexer, struct do_if_trns *do_if, struct dataset *ds)
-{
-  struct expression *condition;
-
-  condition = expr_parse_bool (lexer, NULL, ds);
-  if (condition == NULL)
-    return CMD_CASCADING_FAILURE;
+  finish_clause (ds, do_if);
 
-  add_clause (do_if, condition);
+  add_transformation (ds, &do_if_trns_class, do_if);
 
-  return CMD_SUCCESS;
+  return ok ? CMD_SUCCESS : CMD_FAILURE;
 }
 
-/* Adds a clause to DO_IF that tests for the given CONDITION and,
-   if true, jumps to the set of transformations produced by
-   following commands. */
-static void
-add_clause (struct do_if_trns *do_if, struct expression *condition)
+int
+cmd_inside_do_if (struct lexer *lexer UNUSED, struct dataset *ds UNUSED)
 {
-  struct clause *clause;
-
-  if (do_if->n_clauses > 0)
-    add_transformation (do_if->ds, break_trns_proc, NULL, do_if);
-
-  do_if->clauses = xnrealloc (do_if->clauses,
-                              do_if->n_clauses + 1, sizeof *do_if->clauses);
-  clause = &do_if->clauses[do_if->n_clauses++];
-  clause->condition = condition;
-  clause->target_index = next_transformation (do_if->ds);
+  msg (SE, _("This command cannot appear outside DO IF...END IF."));
+  return CMD_FAILURE;
 }
 
-/* Finalizes DO IF by clearing the control stack, thus ensuring
-   that all open DO IFs are closed. */
-static void
-do_if_finalize_func (void *do_if_ UNUSED)
+static const struct trns_chain *
+do_if_find_clause (const struct do_if_trns *do_if,
+                   struct ccase *c, casenumber case_num)
 {
-  /* This will be called multiple times if multiple DO IFs were
-     executed, which is slightly unclean, but at least it's
-     idempotent. */
-  ctl_stack_clear ();
+  for (size_t i = 0; i < do_if->n_clauses; i++)
+    {
+      const struct clause *clause = &do_if->clauses[i];
+      if (!clause->condition)
+        return &clause->xforms;
+
+      double boolean = expr_evaluate_num (clause->condition, c, case_num);
+      if (boolean != 0.0)
+        return boolean == SYSMIS ? NULL : &clause->xforms;
+    }
+  return NULL;
 }
 
-/* DO IF transformation procedure.
-   Checks each clause and jumps to the appropriate
-   transformation. */
-static int
-do_if_trns_proc (void *do_if_, struct ccase **c, casenumber case_num UNUSED)
+static enum trns_result
+do_if_trns_proc (void *do_if_, struct ccase **c, casenumber case_num)
 {
   struct do_if_trns *do_if = do_if_;
-  struct clause *clause;
 
-  for (clause = do_if->clauses; clause < do_if->clauses + do_if->n_clauses;
-       clause++)
+  const struct trns_chain *chain;
+  size_t start;
+  if (do_if->resume)
+    {
+      chain = do_if->resume;
+      start = do_if->ofs;
+      do_if->resume = NULL;
+      do_if->ofs = 0;
+    }
+  else
     {
-      if (clause->condition != NULL)
+      chain = do_if_find_clause (do_if, *c, case_num);
+      if (!chain)
+        return TRNS_CONTINUE;
+      start = 0;
+    }
+
+  for (size_t i = start; i < chain->n; i++)
+    {
+      const struct transformation *trns = &chain->xforms[i];
+      enum trns_result r = trns->class->execute (trns->aux, c, case_num);
+      switch (r)
         {
-          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;
+        case TRNS_CONTINUE:
+          break;
+
+        case TRNS_BREAK:
+        case TRNS_DROP_CASE:
+        case TRNS_ERROR:
+        case TRNS_END_FILE:
+          return r;
+
+        case TRNS_END_CASE:
+          do_if->resume = chain;
+          do_if->ofs = i;
+          return r;
         }
-      else
-        return clause->target_index;
     }
-  return do_if->past_END_IF_index;
+  return TRNS_CONTINUE;
 }
 
-/* Frees a DO IF transformation. */
 static bool
 do_if_trns_free (void *do_if_)
 {
   struct do_if_trns *do_if = do_if_;
-  struct clause *clause;
 
-  for (clause = do_if->clauses; clause < do_if->clauses + do_if->n_clauses;
-       clause++)
-    expr_free (clause->condition);
+  for (size_t i = 0; i < do_if->n_clauses; i++)
+    {
+      struct clause *clause = &do_if->clauses[i];
+
+      msg_location_destroy (clause->location);
+      expr_free (clause->condition);
+
+      trns_chain_uninit (&clause->xforms);
+    }
   free (do_if->clauses);
   free (do_if);
   return true;
 }
 
-/* Breaks out of a DO IF construct. */
-static int
-break_trns_proc (void *do_if_, struct ccase **c UNUSED,
-                 casenumber 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 const struct ctl_class do_if_class =
-  {
-    "DO IF",
-    "END IF",
-    close_do_if,
-  };
+static const struct trns_class do_if_trns_class = {
+  .name = "DO IF",
+  .execute = do_if_trns_proc,
+  .destroy = do_if_trns_free,
+};
index 156e647ea0e617ac16446a8c8a38d84814b47071..dfc4db2e8977505743466189ef5ebfde18b1bd49 100644 (file)
@@ -16,7 +16,7 @@
 
 #include <config.h>
 
-#include "language/control/control-stack.h"
+#include <limits.h>
 
 #include "data/case.h"
 #include "data/dataset.h"
 #include "data/transformations.h"
 #include "data/variable.h"
 #include "language/command.h"
+#include "language/data-io/inpt-pgm.h"
 #include "language/expressions/public.h"
 #include "language/lexer/lexer.h"
+#include "libpspp/assertion.h"
 #include "libpspp/compiler.h"
 #include "libpspp/message.h"
 #include "libpspp/misc.h"
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
-/* LOOP outputs a transformation that is executed only on the
-   first pass through the loop.  On this trip, it initializes for
-   the first pass by resetting the pass number, setting up the
-   indexing clause, and testing the LOOP IF clause.  If the loop
-   is not to be entered at all, it jumps forward just past the
-   END LOOP transformation; otherwise, it continues to the
-   transformation following LOOP.
-
-   END LOOP outputs a transformation that executes at the end of
-   each trip through the loop.  It checks the END LOOP IF clause,
-   then updates the pass number, increments the indexing clause,
-   and tests the LOOP IF clause.  If another pass through the
-   loop is due, it jumps backward to just after the LOOP
-   transformation; otherwise, it continues to the transformation
-   following END LOOP. */
-
 struct loop_trns
   {
-    struct pool *pool;
-    struct dataset *ds;
-
-    /* Iteration limit. */
-    int max_pass_count;         /* Maximum number of passes (-1=unlimited). */
-    int pass;                  /* Number of passes through the loop so far. */
-
     /* a=a TO b [BY c]. */
-    struct variable *index_var; /* Index variable. */
+    struct variable *index_var;    /* Index variable. */
     struct expression *first_expr; /* Starting index. */
-    struct expression *by_expr;        /* Index increment (default 1.0 if null). */
-    struct expression *last_expr; /* Terminal index. */
-    double cur, by, last;       /* Current value, increment, last value. */
+    struct expression *by_expr;    /* Index increment (or NULL). */
+    struct expression *last_expr;  /* Terminal index. */
 
     /* IF condition for LOOP or END LOOP. */
     struct expression *loop_condition;
     struct expression *end_loop_condition;
 
-    /* Transformation indexes. */
-    int past_LOOP_index;        /* Just past LOOP transformation. */
-    int past_END_LOOP_index;    /* Just past END LOOP transformation. */
+    /* Inner transformations. */
+    struct trns_chain xforms;
+
+    /* State. */
+    double cur, by, last;       /* Index data. */
+    int iteration;              /* For MXLOOPS. */
+    size_t resume_idx;          /* For resuming after END CASE. */
   };
 
-static const struct ctl_class loop_class;
+static struct trns_class loop_trns_class;
 
-static trns_finalize_func loop_trns_finalize;
-static trns_proc_func loop_trns_proc, end_loop_trns_proc, break_trns_proc;
-static trns_free_func loop_trns_free;
+static int in_loop;
 
-static struct loop_trns *create_loop_trns (struct dataset *);
-static bool parse_if_clause (struct lexer *,
-                             struct loop_trns *, struct expression **);
+static bool parse_if_clause (struct lexer *, struct dataset *,
+                             struct expression **);
 static bool parse_index_clause (struct dataset *, struct lexer *,
-                                struct loop_trns *, bool *created_index_var);
-static void close_loop (void *);
+                                struct loop_trns *);
 \f
 /* LOOP. */
 
@@ -98,95 +76,90 @@ static void close_loop (void *);
 int
 cmd_loop (struct lexer *lexer, struct dataset *ds)
 {
-  struct loop_trns *loop;
-  bool created_index_var = false;
-  bool ok = true;
+  struct loop_trns *loop = xmalloc (sizeof *loop);
+  *loop = (struct loop_trns) { .resume_idx = SIZE_MAX };
 
-  loop = create_loop_trns (ds);
+  bool ok = true;
   while (lex_token (lexer) != T_ENDCMD && ok)
     {
       if (lex_match_id (lexer, "IF"))
-        ok = parse_if_clause (lexer, loop, &loop->loop_condition);
+        ok = parse_if_clause (lexer, ds, &loop->loop_condition);
       else
-        ok = parse_index_clause (ds, lexer, loop, &created_index_var);
+        ok = parse_index_clause (ds, lexer, loop);
     }
+  if (ok)
+    lex_end_of_command (lexer);
+  lex_discard_rest_of_command (lexer);
 
-  /* Clean up if necessary. */
-  if (!ok)
+  proc_push_transformations (ds);
+  in_loop++;
+  for (;;)
     {
-      loop->max_pass_count = 0;
-      if (loop->index_var != NULL && created_index_var)
+      if (lex_token (lexer) == T_STOP)
+        {
+          lex_error (lexer, NULL);
+          ok = false;
+          break;
+        }
+      else if (lex_match_phrase (lexer, "END LOOP"))
         {
-          dict_delete_var (dataset_dict (ds), loop->index_var);
-          loop->index_var = NULL;
+          if (lex_match_id (lexer, "IF"))
+            ok = parse_if_clause (lexer, ds, &loop->end_loop_condition) && ok;
+          break;
         }
+      else
+        cmd_parse_in_state (lexer, ds,
+                            (in_input_program ()
+                             ? CMD_STATE_NESTED_INPUT_PROGRAM
+                             : CMD_STATE_NESTED_DATA));
     }
+  in_loop--;
+  proc_pop_transformations (ds, &loop->xforms);
+
+  add_transformation (ds, &loop_trns_class, loop);
 
-  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+  return ok ? CMD_SUCCESS : CMD_FAILURE;
 }
 
-/* Parses END LOOP. */
 int
-cmd_end_loop (struct lexer *lexer, struct dataset *ds)
+cmd_inside_loop (struct lexer *lexer UNUSED, struct dataset *ds UNUSED)
 {
-  struct loop_trns *loop;
-  bool ok = true;
-
-  loop = ctl_stack_top (&loop_class);
-  if (loop == NULL)
-    return CMD_CASCADING_FAILURE;
-
-  assert (loop->ds == ds);
-
-  /* Parse syntax. */
-  if (lex_match_id (lexer, "IF"))
-    ok = parse_if_clause (lexer, loop, &loop->end_loop_condition);
-  if (ok)
-    ok = lex_end_of_command (lexer) == CMD_SUCCESS;
-
-  if (!ok)
-    loop->max_pass_count = 0;
-
-  ctl_stack_pop (loop);
+  msg (SE, _("This command cannot appear outside LOOP...END LOOP."));
+  return CMD_FAILURE;
+}
 
-  return ok ? CMD_SUCCESS : CMD_FAILURE;
+static enum trns_result
+break_trns_proc (void *aux UNUSED, struct ccase **c UNUSED,
+                 casenumber case_num UNUSED)
+{
+  return TRNS_BREAK;
 }
 
 /* Parses BREAK. */
 int
-cmd_break (struct lexer *lexer UNUSED, struct dataset *ds)
+cmd_break (struct lexer *lexer, struct dataset *ds)
 {
-  struct ctl_stmt *loop = ctl_stack_search (&loop_class);
-  if (loop == NULL)
-    return CMD_CASCADING_FAILURE;
+  if (!in_loop)
+    {
+      cmd_inside_loop (lexer, ds);
+      return CMD_FAILURE;
+    }
 
-  add_transformation (ds, break_trns_proc, NULL, loop);
+  static const struct trns_class trns_class = {
+    .name = "BREAK",
+    .execute = break_trns_proc
+  };
+  add_transformation (ds, &trns_class, NULL);
 
   return CMD_SUCCESS;
 }
 
-/* Closes a LOOP construct by emitting the END LOOP
-   transformation and finalizing its members appropriately. */
-static void
-close_loop (void *loop_)
-{
-  struct loop_trns *loop = loop_;
-
-  add_transformation (loop->ds, end_loop_trns_proc, NULL, loop);
-  loop->past_END_LOOP_index = next_transformation (loop->ds);
-
-  /* If there's nothing else limiting the number of loops, use
-     MXLOOPS as a limit. */
-  if (loop->max_pass_count == -1 && loop->index_var == NULL)
-    loop->max_pass_count = settings_get_mxloops ();
-}
-
 /* Parses an IF clause for LOOP or END LOOP and stores the
    resulting expression to *CONDITION.
    Returns true if successful, false on failure. */
 static bool
-parse_if_clause (struct lexer *lexer,
-                struct loop_trns *loop, struct expression **condition)
+parse_if_clause (struct lexer *lexer, struct dataset *ds,
+                struct expression **condition)
 {
   if (*condition != NULL)
     {
@@ -194,17 +167,15 @@ parse_if_clause (struct lexer *lexer,
       return false;
     }
 
-  *condition = expr_parse_bool (lexer, loop->pool, loop->ds);
+  *condition = expr_parse_bool (lexer, ds);
   return *condition != NULL;
 }
 
-/* Parses an indexing clause into LOOP.
-   Stores true in *CREATED_INDEX_VAR if the index clause created
-   a new variable, false otherwise.
-   Returns true if successful, false on failure. */
+/* Parses an indexing clause into LOOP.  Returns true if successful, false on
+   failure. */
 static bool
 parse_index_clause (struct dataset *ds, struct lexer *lexer,
-                    struct loop_trns *loop, bool *created_index_var)
+                    struct loop_trns *loop)
 {
   if (loop->index_var != NULL)
     {
@@ -219,20 +190,15 @@ parse_index_clause (struct dataset *ds, struct lexer *lexer,
     }
 
   loop->index_var = dict_lookup_var (dataset_dict (ds), lex_tokcstr (lexer));
-  if (loop->index_var != NULL)
-    *created_index_var = false;
-  else
-    {
-      loop->index_var = dict_create_var_assert (dataset_dict (ds),
-                                                lex_tokcstr (lexer), 0);
-      *created_index_var = true;
-    }
+  if (!loop->index_var)
+    loop->index_var = dict_create_var_assert (dataset_dict (ds),
+                                              lex_tokcstr (lexer), 0);
   lex_get (lexer);
 
   if (!lex_force_match (lexer, T_EQUALS))
     return false;
 
-  loop->first_expr = expr_parse (lexer, loop->pool, loop->ds, VAL_NUMERIC);
+  loop->first_expr = expr_parse (lexer, ds, VAL_NUMERIC);
   if (loop->first_expr == NULL)
     return false;
 
@@ -251,7 +217,7 @@ parse_index_clause (struct dataset *ds, struct lexer *lexer,
           lex_sbc_only_once (e == &loop->last_expr ? "TO" : "BY");
           return false;
         }
-      *e = expr_parse (lexer, loop->pool, loop->ds, VAL_NUMERIC);
+      *e = expr_parse (lexer, ds, VAL_NUMERIC);
       if (*e == NULL)
         return false;
     }
@@ -260,56 +226,28 @@ parse_index_clause (struct dataset *ds, struct lexer *lexer,
       lex_sbc_missing ("TO");
       return false;
     }
-  if (loop->by_expr == NULL)
-    loop->by = 1.0;
 
   return true;
 }
 
-/* Creates, initializes, and returns a new loop_trns. */
-static struct loop_trns *
-create_loop_trns (struct dataset *ds)
-{
-  struct loop_trns *loop = pool_create_container (struct loop_trns, pool);
-  loop->max_pass_count = -1;
-  loop->pass = 0;
-  loop->index_var = NULL;
-  loop->first_expr = loop->by_expr = loop->last_expr = NULL;
-  loop->loop_condition = loop->end_loop_condition = NULL;
-  loop->ds = ds;
-
-  add_transformation_with_finalizer (ds, loop_trns_finalize,
-                                     loop_trns_proc, loop_trns_free, loop);
-  loop->past_LOOP_index = next_transformation (ds);
-
-  ctl_stack_push (&loop_class, loop);
-
-  return loop;
-}
-
-/* Finalizes LOOP by clearing the control stack, thus ensuring
-   that all open LOOPs are closed. */
-static void
-loop_trns_finalize (void *do_if_ UNUSED)
-{
-  /* This will be called multiple times if multiple LOOPs were
-     executed, which is slightly unclean, but at least it's
-     idempotent. */
-  ctl_stack_clear ();
-}
-
 /* Sets up LOOP for the first pass. */
-static int
+static enum trns_result
 loop_trns_proc (void *loop_, struct ccase **c, casenumber case_num)
 {
   struct loop_trns *loop = loop_;
 
-  if (loop->index_var != NULL)
+  size_t start_idx = loop->resume_idx;
+  loop->resume_idx = SIZE_MAX;
+  if (start_idx != SIZE_MAX)
+    goto resume;
+
+  if (loop->index_var)
     {
       /* Evaluate loop index expressions. */
       loop->cur = expr_evaluate_num (loop->first_expr, *c, case_num);
-      if (loop->by_expr != NULL)
-       loop->by = expr_evaluate_num (loop->by_expr, *c, case_num);
+      loop->by = (loop->by_expr
+                  ? expr_evaluate_num (loop->by_expr, *c, case_num)
+                  : 1.0);
       loop->last = expr_evaluate_num (loop->last_expr, *c, case_num);
 
       /* Even if the loop is never entered, set the index
@@ -318,28 +256,65 @@ loop_trns_proc (void *loop_, struct ccase **c, casenumber case_num)
       *case_num_rw (*c, loop->index_var) = loop->cur;
 
       /* Throw out pathological cases. */
-      if (!isfinite (loop->cur) || !isfinite (loop->by)
+      if (!isfinite (loop->cur)
+          || !isfinite (loop->by)
           || !isfinite (loop->last)
           || loop->by == 0.0
           || (loop->by > 0.0 && loop->cur > loop->last)
           || (loop->by < 0.0 && loop->cur < loop->last))
-        goto zero_pass;
+        return TRNS_CONTINUE;
     }
 
-  /* Initialize pass count. */
-  loop->pass = 0;
-  if (loop->max_pass_count >= 0 && loop->pass >= loop->max_pass_count)
-    goto zero_pass;
+  for (loop->iteration = 0;
+       loop->index_var || loop->iteration < settings_get_mxloops ();
+       loop->iteration++)
+    {
+      if (loop->loop_condition
+          && expr_evaluate_num (loop->loop_condition, *c, case_num) != 1.0)
+        break;
+
+      start_idx = 0;
+    resume:
+      for (size_t i = start_idx; i < loop->xforms.n; i++)
+        {
+          const struct transformation *trns = &loop->xforms.xforms[i];
+          enum trns_result r = trns->class->execute (trns->aux, c, case_num);
+          switch (r)
+            {
+            case TRNS_CONTINUE:
+              break;
+
+            case TRNS_BREAK:
+              return TRNS_CONTINUE;
+
+            case TRNS_END_CASE:
+              loop->resume_idx = i;
+              return TRNS_END_CASE;
+
+            case TRNS_ERROR:
+            case TRNS_END_FILE:
+              return r;
+
+            case TRNS_DROP_CASE:
+              NOT_REACHED ();
+            }
+        }
 
-  /* Check condition. */
-  if (loop->loop_condition != NULL
-      && expr_evaluate_num (loop->loop_condition, *c, case_num) != 1.0)
-    goto zero_pass;
+      if (loop->end_loop_condition != NULL
+          && expr_evaluate_num (loop->end_loop_condition, *c, case_num) != 0.0)
+        break;
 
-  return loop->past_LOOP_index;
+      if (loop->index_var)
+        {
+          loop->cur += loop->by;
+          if (loop->by > 0.0 ? loop->cur > loop->last : loop->cur < loop->last)
+            break;
 
- zero_pass:
-  return loop->past_END_LOOP_index;
+          *c = case_unshare (*c);
+          *case_num_rw (*c, loop->index_var) = loop->cur;
+        }
+    }
+  return TRNS_CONTINUE;
 }
 
 /* Frees LOOP. */
@@ -348,59 +323,21 @@ loop_trns_free (void *loop_)
 {
   struct loop_trns *loop = loop_;
 
-  pool_destroy (loop->pool);
-  return true;
-}
-
-/* Finishes a pass through the loop and starts the next. */
-static int
-end_loop_trns_proc (void *loop_, struct ccase **c, casenumber case_num UNUSED)
-{
-  struct loop_trns *loop = loop_;
-
-  if (loop->end_loop_condition != NULL
-      && expr_evaluate_num (loop->end_loop_condition, *c, case_num) != 0.0)
-    goto break_out;
-
-  /* MXLOOPS limiter. */
-  if (loop->max_pass_count >= 0 && ++loop->pass >= loop->max_pass_count)
-    goto break_out;
-
-  /* Indexing clause limiter: counting downward. */
-  if (loop->index_var != NULL)
-    {
-      loop->cur += loop->by;
-      if ((loop->by > 0.0 && loop->cur > loop->last)
-          || (loop->by < 0.0 && loop->cur < loop->last))
-        goto break_out;
-      *c = case_unshare (*c);
-      *case_num_rw (*c, loop->index_var) = loop->cur;
-    }
-
-  if (loop->loop_condition != NULL
-      && expr_evaluate_num (loop->loop_condition, *c, case_num) != 1.0)
-    goto break_out;
+  expr_free (loop->first_expr);
+  expr_free (loop->by_expr);
+  expr_free (loop->last_expr);
 
-  return loop->past_LOOP_index;
+  expr_free (loop->loop_condition);
+  expr_free (loop->end_loop_condition);
 
- break_out:
-  return loop->past_END_LOOP_index;
-}
-
-/* Executes BREAK. */
-static int
-break_trns_proc (void *loop_, struct ccase **c UNUSED,
-                 casenumber case_num UNUSED)
-{
-  struct loop_trns *loop = loop_;
+  trns_chain_uninit (&loop->xforms);
 
-  return loop->past_END_LOOP_index;
+  free (loop);
+  return true;
 }
 
-/* LOOP control structure class definition. */
-static const struct ctl_class loop_class =
-  {
-    "LOOP",
-    "END LOOP",
-    close_loop,
-  };
+static struct trns_class loop_trns_class = {
+  .name = "LOOP",
+  .execute = loop_trns_proc,
+  .destroy = loop_trns_free,
+};
index b350759b024e3c0d2393f0bf32ddf50313dcbecc..c7514413727424dd7c455aaf28f0e65a7c9d1a44 100644 (file)
@@ -24,7 +24,6 @@
 #include "data/value-labels.h"
 #include "data/variable.h"
 #include "language/command.h"
-#include "language/control/control-stack.h"
 #include "language/lexer/lexer.h"
 #include "libpspp/message.h"
 #include "libpspp/str.h"
index 02cabd8cde0ca3df01042945eec85299df164e2b..1ade0dc617c566c885001a21634ea37d55df438b 100644 (file)
@@ -67,8 +67,7 @@ static bool parse_fixed (struct lexer *, struct dictionary *,
 static bool parse_free (struct lexer *, struct dictionary *,
                         struct pool *, struct data_parser *);
 
-static trns_free_func data_list_trns_free;
-static trns_proc_func data_list_trns_proc;
+static const struct trns_class data_list_trns_class;
 
 int
 cmd_data_list (struct lexer *lexer, struct dataset *ds)
@@ -295,7 +294,7 @@ cmd_data_list (struct lexer *lexer, struct dataset *ds)
       trns->parser = parser;
       trns->reader = reader;
       trns->end = end;
-      add_transformation (ds, data_list_trns_proc, data_list_trns_free, trns);
+      add_transformation (ds, &data_list_trns_class, trns);
     }
   else
     data_parser_make_active_file (parser, ds, reader, dict, NULL, NULL);
@@ -303,7 +302,9 @@ cmd_data_list (struct lexer *lexer, struct dataset *ds)
   fh_unref (fh);
   free (encoding);
 
-  return CMD_DATA_LIST;
+  data_list_seen ();
+
+  return CMD_SUCCESS;
 
  error:
   data_parser_destroy (parser);
@@ -511,11 +512,11 @@ data_list_trns_free (void *trns_)
 }
 
 /* Handle DATA LIST transformation TRNS, parsing data into *C. */
-static int
+static enum trns_result
 data_list_trns_proc (void *trns_, struct ccase **c, casenumber case_num UNUSED)
 {
   struct data_list_trns *trns = trns_;
-  int retval;
+  enum trns_result retval;
 
   *c = case_unshare (*c);
   if (data_parser_parse (trns->parser, trns->reader, *c))
@@ -544,4 +545,9 @@ data_list_trns_proc (void *trns_, struct ccase **c, casenumber case_num UNUSED)
 
   return retval;
 }
-\f
+
+static const struct trns_class data_list_trns_class = {
+  .name = "DATA LIST",
+  .execute = data_list_trns_proc,
+  .destroy = data_list_trns_free,
+};
index e2cfbd1bb8899e52b450890a13dc98999b4fe4d5..b9e2c326feb86374b6e17f7afc70568b38d61e63 100644 (file)
@@ -49,8 +49,10 @@ struct input_program_pgm
   {
     struct session *session;
     struct dataset *ds;
-    struct trns_chain *trns_chain;
-    enum trns_result restart;
+
+    struct trns_chain xforms;
+    size_t idx;
+    bool eof;
 
     casenumber case_nr;             /* Incremented by END CASE transformation. */
 
@@ -59,14 +61,16 @@ struct input_program_pgm
   };
 
 static void destroy_input_program (struct input_program_pgm *);
-static trns_proc_func end_case_trns_proc;
-static trns_proc_func reread_trns_proc;
-static trns_proc_func end_file_trns_proc;
-static trns_free_func reread_trns_free;
+static const struct trns_class end_case_trns_class;
+static const struct trns_class reread_trns_class;
+static const struct trns_class end_file_trns_class;
 
 static const struct casereader_class input_program_casereader_class;
 
 static bool inside_input_program;
+static bool saw_END_CASE;
+static bool saw_END_FILE;
+static bool saw_DATA_LIST;
 
 /* Returns true if we're parsing the inside of a INPUT
    PROGRAM...END INPUT PROGRAM construct, false otherwise. */
@@ -76,70 +80,56 @@ in_input_program (void)
   return inside_input_program;
 }
 
+void
+data_list_seen (void)
+{
+  saw_DATA_LIST = true;
+}
+
 /* Emits an END CASE transformation for INP. */
 static void
-emit_END_CASE (struct dataset *ds, struct input_program_pgm *inp)
+emit_END_CASE (struct dataset *ds)
 {
-  add_transformation (ds, end_case_trns_proc, NULL, inp);
+  add_transformation (ds, &end_case_trns_class, xzalloc (sizeof (bool)));
 }
 
 int
 cmd_input_program (struct lexer *lexer, struct dataset *ds)
 {
-  struct input_program_pgm *inp;
-  bool saw_END_CASE = false;
-  bool saw_END_FILE = false;
-  bool saw_DATA_LIST = false;
-
   if (!lex_match (lexer, T_ENDCMD))
     return lex_end_of_command (lexer);
 
-  inp = xmalloc (sizeof *inp);
-  inp->session = session_create (dataset_session (ds));
-  inp->ds = dataset_create (inp->session, "INPUT PROGRAM");
-  inp->trns_chain = NULL;
-  inp->init = NULL;
-  inp->proto = NULL;
+  struct session *session = session_create (dataset_session (ds));
+  struct dataset *inp_ds = dataset_create (session, "INPUT PROGRAM");
+
+  struct input_program_pgm *inp = xmalloc (sizeof *inp);
+  *inp = (struct input_program_pgm) { .session = session, .ds = inp_ds };
 
+  proc_push_transformations (inp->ds);
   inside_input_program = true;
+  saw_END_CASE = saw_END_FILE = saw_DATA_LIST = false;
   while (!lex_match_phrase (lexer, "END INPUT PROGRAM"))
     {
       enum cmd_result result;
 
       result = cmd_parse_in_state (lexer, inp->ds, CMD_STATE_INPUT_PROGRAM);
-      switch (result)
+      if (result == CMD_EOF
+          || result == CMD_FINISH
+          || result == CMD_CASCADING_FAILURE)
         {
-        case CMD_DATA_LIST:
-          saw_DATA_LIST = true;
-          break;
-
-        case CMD_END_CASE:
-          emit_END_CASE (inp->ds, inp);
-          saw_END_CASE = true;
-          break;
-
-        case CMD_END_FILE:
-          saw_END_FILE = true;
-          break;
-
-        case CMD_FAILURE:
-          break;
+          proc_pop_transformations (inp->ds, &inp->xforms);
 
-        default:
-          if (cmd_result_is_failure (result)
-              && lex_get_error_mode (lexer) != LEX_ERROR_TERMINAL)
-            {
-              if (result == CMD_EOF)
-                msg (SE, _("Unexpected end-of-file within %s."), "INPUT PROGRAM");
-              inside_input_program = false;
-              destroy_input_program (inp);
-              return result;
-            }
+          if (result == CMD_EOF)
+            msg (SE, _("Unexpected end-of-file within %s."), "INPUT PROGRAM");
+          inside_input_program = false;
+          destroy_input_program (inp);
+          return result;
         }
     }
   if (!saw_END_CASE)
-    emit_END_CASE (inp->ds, inp);
+    emit_END_CASE (inp->ds);
   inside_input_program = false;
+  proc_pop_transformations (inp->ds, &inp->xforms);
 
   if (!saw_DATA_LIST && !saw_END_FILE)
     {
@@ -154,11 +144,6 @@ cmd_input_program (struct lexer *lexer, struct dataset *ds)
       return CMD_FAILURE;
     }
 
-  inp->trns_chain = proc_capture_transformations (inp->ds);
-  trns_chain_finalize (inp->trns_chain);
-
-  inp->restart = TRNS_CONTINUE;
-
   /* Figure out how to initialize each input case. */
   inp->init = caseinit_create ();
   caseinit_mark_for_init (inp->init, dataset_dict (inp->ds));
@@ -172,28 +157,6 @@ cmd_input_program (struct lexer *lexer, struct dataset *ds)
   return CMD_SUCCESS;
 }
 
-int
-cmd_end_input_program (struct lexer *lexer UNUSED, struct dataset *ds UNUSED)
-{
-  /* Inside INPUT PROGRAM, this should get caught at the top of the loop in
-     cmd_input_program().
-
-     Outside of INPUT PROGRAM, the command parser should reject this
-     command. */
-  NOT_REACHED ();
-}
-
-/* Returns true if STATE is valid given the transformations that
-   are allowed within INPUT PROGRAM. */
-static bool
-is_valid_state (enum trns_result state)
-{
-  return (state == TRNS_CONTINUE
-          || state == TRNS_ERROR
-          || state == TRNS_END_FILE
-          || state >= 0);
-}
-
 /* Reads and returns one case.
    Returns the case if successful, null at end of file or if an
    I/O error occurred. */
@@ -201,27 +164,46 @@ static struct ccase *
 input_program_casereader_read (struct casereader *reader UNUSED, void *inp_)
 {
   struct input_program_pgm *inp = inp_;
+
+  if (inp->eof || !inp->xforms.n)
+    return NULL;
+
   struct ccase *c = case_create (inp->proto);
+  caseinit_init_vars (inp->init, c);
 
-  do
+  for (size_t i = inp->idx < inp->xforms.n ? inp->idx : 0; ; i++)
     {
-      assert (is_valid_state (inp->restart));
-      if (inp->restart == TRNS_ERROR || inp->restart == TRNS_END_FILE)
+      if (i >= inp->xforms.n)
+        {
+          i = 0;
+          c = case_unshare (c);
+          caseinit_update_left_vars (inp->init, c);
+          caseinit_init_vars (inp->init, c);
+        }
+
+      const struct transformation *trns = &inp->xforms.xforms[i];
+      switch (trns->class->execute (trns->aux, &c, inp->case_nr))
         {
+        case TRNS_END_CASE:
+          inp->case_nr++;
+          inp->idx = i;
+          return c;
+
+        case TRNS_ERROR:
+          casereader_force_error (reader);
+          /* Fall through. */
+        case TRNS_END_FILE:
+          inp->eof = true;
           case_unref (c);
           return NULL;
-        }
 
-      c = case_unshare (c);
-      caseinit_init_vars (inp->init, c);
-      inp->restart = trns_chain_execute (inp->trns_chain, inp->restart,
-                                         &c, inp->case_nr);
-      assert (is_valid_state (inp->restart));
-      caseinit_update_left_vars (inp->init, c);
-    }
-  while (inp->restart < 0);
+        case TRNS_CONTINUE:
+          break;
 
-  return c;
+        default:
+          NOT_REACHED ();
+        }
+    }
 }
 
 static void
@@ -230,7 +212,7 @@ destroy_input_program (struct input_program_pgm *pgm)
   if (pgm != NULL)
     {
       session_destroy (pgm->session);
-      trns_chain_destroy (pgm->trns_chain);
+      trns_chain_uninit (&pgm->xforms);
       caseinit_destroy (pgm->init);
       caseproto_unref (pgm->proto);
       free (pgm);
@@ -239,11 +221,9 @@ destroy_input_program (struct input_program_pgm *pgm)
 
 /* Destroys the casereader. */
 static void
-input_program_casereader_destroy (struct casereader *reader, void *inp_)
+input_program_casereader_destroy (struct casereader *reader UNUSED, void *inp_)
 {
   struct input_program_pgm *inp = inp_;
-  if (inp->restart == TRNS_ERROR)
-    casereader_force_error (reader);
   destroy_input_program (inp);
 }
 
@@ -256,24 +236,38 @@ static const struct casereader_class input_program_casereader_class =
   };
 \f
 int
-cmd_end_case (struct lexer *lexer, struct dataset *ds UNUSED)
+cmd_end_case (struct lexer *lexer UNUSED, struct dataset *ds)
 {
   assert (in_input_program ());
-  if (lex_token (lexer) == T_ENDCMD)
-    return CMD_END_CASE;
+  emit_END_CASE (ds);
+  saw_END_CASE = true;
   return CMD_SUCCESS;
 }
 
 /* Outputs the current case */
-int
-end_case_trns_proc (void *inp_, struct ccase **c UNUSED,
+static enum trns_result
+end_case_trns_proc (void *resume_, struct ccase **c UNUSED,
                     casenumber case_nr UNUSED)
 {
-  struct input_program_pgm *inp = inp_;
-  inp->case_nr++;
-  return TRNS_END_CASE;
+  bool *resume = resume_;
+  enum trns_result retval = *resume ? TRNS_CONTINUE : TRNS_END_CASE;
+  *resume = !*resume;
+  return retval;
 }
 
+static bool
+end_case_trns_free (void *resume)
+{
+  free (resume);
+  return true;
+}
+
+static const struct trns_class end_case_trns_class = {
+  .name = "END CASE",
+  .execute = end_case_trns_proc,
+  .destroy = end_case_trns_free,
+};
+
 /* REREAD transformation. */
 struct reread_trns
   {
@@ -304,7 +298,7 @@ cmd_reread (struct lexer *lexer, struct dataset *ds)
               goto error;
            }
 
-         e = expr_parse (lexer, NULL, ds, VAL_NUMERIC);
+         e = expr_parse (lexer, ds, VAL_NUMERIC);
          if (!e)
             goto error;
        }
@@ -337,7 +331,7 @@ cmd_reread (struct lexer *lexer, struct dataset *ds)
   t = xmalloc (sizeof *t);
   t->reader = dfm_open_reader (fh, lexer, encoding);
   t->column = e;
-  add_transformation (ds, reread_trns_proc, reread_trns_free, t);
+  add_transformation (ds, &reread_trns_class, t);
 
   fh_unref (fh);
   free (encoding);
@@ -350,7 +344,7 @@ error:
 }
 
 /* Executes a REREAD transformation. */
-static int
+static enum trns_result
 reread_trns_proc (void *t_, struct ccase **c, casenumber case_num)
 {
   struct reread_trns *t = t_;
@@ -383,21 +377,33 @@ reread_trns_free (void *t_)
   return true;
 }
 
+static const struct trns_class reread_trns_class = {
+  .name = "REREAD",
+  .execute = reread_trns_proc,
+  .destroy = reread_trns_free,
+};
+
 /* Parses END FILE command. */
 int
 cmd_end_file (struct lexer *lexer UNUSED, struct dataset *ds)
 {
   assert (in_input_program ());
 
-  add_transformation (ds, end_file_trns_proc, NULL, NULL);
+  add_transformation (ds, &end_file_trns_class, NULL);
+  saw_END_FILE = true;
 
-  return CMD_END_FILE;
+  return CMD_SUCCESS;
 }
 
 /* Executes an END FILE transformation. */
-static int
+static enum trns_result
 end_file_trns_proc (void *trns_ UNUSED, struct ccase **c UNUSED,
                     casenumber case_num UNUSED)
 {
   return TRNS_END_FILE;
 }
+
+static const struct trns_class end_file_trns_class = {
+  .name = "END FILE",
+  .execute = end_file_trns_proc,
+};
index cac4d9b6d709b978f827e0dc60f122703cd803cf..d6c5bbd63d73290d305117bf4480bc74f7b01bae 100644 (file)
@@ -20,6 +20,7 @@
 #include <stdbool.h>
 
 bool in_input_program (void);
-void cancel_input_program (void);
+
+void data_list_seen (void);
 
 #endif /* inpt-pgm.h */
index f077e9c39573e30760303704d0c356e95895bbf7..b908927bdf6415438542a708f92050761575b9e0 100644 (file)
@@ -41,8 +41,7 @@ struct print_space_trns
     struct expression *expr;   /* Number of lines; NULL means 1. */
   };
 
-static trns_proc_func print_space_trns_proc;
-static trns_free_func print_space_trns_free;
+static const struct trns_class print_space_class;
 
 int
 cmd_print_space (struct lexer *lexer, struct dataset *ds)
@@ -77,7 +76,7 @@ cmd_print_space (struct lexer *lexer, struct dataset *ds)
 
   if (lex_token (lexer) != T_ENDCMD)
     {
-      expr = expr_parse (lexer, NULL, ds, VAL_NUMERIC);
+      expr = expr_parse (lexer, ds, VAL_NUMERIC);
       if (lex_token (lexer) != T_ENDCMD)
        {
           lex_error (lexer, _("expecting end of command"));
@@ -100,8 +99,7 @@ cmd_print_space (struct lexer *lexer, struct dataset *ds)
   trns->writer = writer;
   trns->expr = expr;
 
-  add_transformation (ds,
-                     print_space_trns_proc, print_space_trns_free, trns);
+  add_transformation (ds, &print_space_class, trns);
   fh_unref (handle);
   return CMD_SUCCESS;
 
@@ -112,7 +110,7 @@ error:
 }
 
 /* Executes a PRINT SPACE transformation. */
-static int
+static enum trns_result
 print_space_trns_proc (void *t_, struct ccase **c,
                        casenumber case_num UNUSED)
 {
@@ -154,3 +152,9 @@ print_space_trns_free (void *trns_)
   free (trns);
   return ok;
 }
+
+static const struct trns_class print_space_class = {
+  .name = "PRINT SPACE",
+  .execute = print_space_trns_proc,
+  .destroy = print_space_trns_free,
+};
index 5c9ef7af993e903c0b0ebf18781e7379b525611f..657d53c35074a23e2660717937001fae087646fd 100644 (file)
@@ -96,13 +96,16 @@ enum which_formats
     WRITE
   };
 
+static const struct trns_class print_binary_trns_class;
+static const struct trns_class print_text_trns_class;
+
 static int internal_cmd_print (struct lexer *, struct dataset *ds,
                               enum which_formats, bool eject);
-static trns_proc_func print_text_trns_proc, print_binary_trns_proc;
-static trns_free_func print_trns_free;
 static bool parse_specs (struct lexer *, struct pool *tmp_pool, struct print_trns *,
                         struct dictionary *dict, enum which_formats);
 static void dump_table (struct print_trns *);
+
+static bool print_trns_free (void *trns_);
 \f
 /* Basic parsing. */
 
@@ -239,11 +242,9 @@ internal_cmd_print (struct lexer *lexer, struct dataset *ds,
     dump_table (trns);
 
   /* Put the transformation in the queue. */
-  add_transformation (ds,
-                      (binary
-                       ? print_binary_trns_proc
-                       : print_text_trns_proc),
-                      print_trns_free, trns);
+  add_transformation (ds, (binary
+                           ? &print_binary_trns_class
+                           : &print_text_trns_class), trns);
 
   pool_destroy (tmp_pool);
   fh_unref (fh);
@@ -474,7 +475,7 @@ static void print_text_flush_records (struct print_trns *, struct u8_line *,
                                       bool *eject, int *record);
 
 /* Performs the transformation inside print_trns T on case C. */
-static int
+static enum trns_result
 print_text_trns_proc (void *trns_, struct ccase **c,
                       casenumber case_num UNUSED)
 {
@@ -587,7 +588,7 @@ static void print_binary_flush_records (struct print_trns *,
                                         bool *eject, int *record);
 
 /* Performs the transformation inside print_trns T on case C. */
-static int
+static enum trns_result
 print_binary_trns_proc (void *trns_, struct ccase **c,
                         casenumber case_num UNUSED)
 {
@@ -685,3 +686,15 @@ print_trns_free (void *trns_)
   return ok;
 }
 
+static const struct trns_class print_binary_trns_class = {
+  .name = "PRINT",
+  .execute = print_binary_trns_proc,
+  .destroy = print_trns_free,
+};
+
+static const struct trns_class print_text_trns_class = {
+  .name = "PRINT",
+  .execute = print_text_trns_proc,
+  .destroy = print_trns_free,
+};
+
index a5848e59f5ce12f08b5de428df5fb7154cc9ed09..88f3c75ee59a23d66a628995843d3b6dbaf23f27 100644 (file)
@@ -99,8 +99,7 @@ struct output_trns
     struct casewriter *writer;          /* Writer. */
   };
 
-static trns_proc_func output_trns_proc;
-static trns_free_func output_trns_free;
+static const struct trns_class output_trns_class;
 static struct casewriter *parse_write_command (struct lexer *,
                                                struct dataset *,
                                                enum writer_type,
@@ -140,7 +139,7 @@ parse_output_trns (struct lexer *lexer, struct dataset *ds, enum writer_type wri
       return CMD_CASCADING_FAILURE;
     }
 
-  add_transformation (ds, output_trns_proc, output_trns_free, t);
+  add_transformation (ds, &output_trns_class, t);
   return CMD_SUCCESS;
 }
 
@@ -364,7 +363,7 @@ parse_write_command (struct lexer *lexer, struct dataset *ds,
 }
 
 /* Writes case *C to the system file specified on XSAVE or XEXPORT. */
-static int
+static enum trns_result
 output_trns_proc (void *trns_, struct ccase **c, casenumber case_num UNUSED)
 {
   struct output_trns *t = trns_;
@@ -382,3 +381,9 @@ output_trns_free (void *trns_)
   free (t);
   return ok;
 }
+
+static const struct trns_class output_trns_class = {
+  .name = "XSAVE/XEXPORT",
+  .execute = output_trns_proc,
+  .destroy = output_trns_free,
+};
index ab603c0b892d987c1f50dbdd9e6a26a03331fa50..22c7a410802d2fb4556f7cfa0a541b90b34f3e20 100644 (file)
@@ -68,12 +68,9 @@ static union any_node *allocate_unary_variable (struct expression *,
 /* Parses an expression of the given TYPE.  If DS is nonnull then variables and
    vectors within it may be referenced within the expression; otherwise, the
    expression must not reference any variables or vectors.  Returns the new
-   expression if successful or a null pointer otherwise.  If POOL is nonnull,
-   then destroying POOL will free the expression; otherwise, the caller must
-   eventually free it with expr_free(). */
+   expression if successful or a null pointer otherwise. */
 struct expression *
-expr_parse (struct lexer *lexer, struct pool *pool, struct dataset *ds,
-            enum val_type type)
+expr_parse (struct lexer *lexer, struct dataset *ds, enum val_type type)
 {
   assert (val_type_is_valid (type));
 
@@ -85,15 +82,12 @@ expr_parse (struct lexer *lexer, struct pool *pool, struct dataset *ds,
       return NULL;
     }
 
-  e = finish_expression (expr_optimize (n, e), e);
-  if (pool)
-    pool_add_subpool (pool, e->expr_pool);
-  return e;
+  return finish_expression (expr_optimize (n, e), e);
 }
 
 /* Parses a boolean expression, otherwise similar to expr_parse(). */
 struct expression *
-expr_parse_bool (struct lexer *lexer, struct pool *pool, struct dataset *ds)
+expr_parse_bool (struct lexer *lexer, struct dataset *ds)
 {
   struct expression *e = expr_create (ds);
   union any_node *n = parse_or (lexer, e);
@@ -116,17 +110,14 @@ expr_parse_bool (struct lexer *lexer, struct pool *pool, struct dataset *ds)
       return NULL;
     }
 
-  e = finish_expression (expr_optimize (n, e), e);
-  if (pool)
-    pool_add_subpool (pool, e->expr_pool);
-  return e;
+  return finish_expression (expr_optimize (n, e), e);
 }
 
 /* Parses a numeric expression that is intended to be assigned to newly created
    variable NEW_VAR_NAME.  (This allows for a better error message if the
    expression is not numeric.)  Otherwise similar to expr_parse(). */
 struct expression *
-expr_parse_new_variable (struct lexer *lexer, struct pool *pool, struct dataset *ds,
+expr_parse_new_variable (struct lexer *lexer, struct dataset *ds,
                          const char *new_var_name)
 {
   struct expression *e = expr_create (ds);
@@ -149,10 +140,7 @@ expr_parse_new_variable (struct lexer *lexer, struct pool *pool, struct dataset
       return NULL;
     }
 
-  e = finish_expression (expr_optimize (n, e), e);
-  if (pool)
-    pool_add_subpool (pool, e->expr_pool);
-  return e;
+  return finish_expression (expr_optimize (n, e), e);
 }
 
 /* Free expression E. */
index a4313a40c0aff4750353c2e5fceb5498e4b809bb..6eb539166b9237278dd7d843f75d26d74aeb1c04 100644 (file)
@@ -29,12 +29,9 @@ struct lexer;
 struct pool;
 union value;
 
-struct expression *expr_parse (struct lexer *lexer, struct pool *,
-                               struct dataset *, enum val_type);
-struct expression *expr_parse_bool (struct lexer *lexer, struct pool *,
-                                    struct dataset *);
-struct expression *expr_parse_new_variable (struct lexer *lexer, struct pool *,
-                                            struct dataset *,
+struct expression *expr_parse (struct lexer *, struct dataset *, enum val_type);
+struct expression *expr_parse_bool (struct lexer *, struct dataset *);
+struct expression *expr_parse_new_variable (struct lexer *, struct dataset *,
                                             const char *new_var_name);
 void expr_free (struct expression *);
 
index 1a74b1e4a9e4e3365e5fc77ca0fcbeb05147a654..2d569a0436eed60105da88150650cc1ae23e9811 100644 (file)
@@ -96,8 +96,7 @@ struct autorecode_pgm
   bool blank_valid;
 };
 
-static trns_proc_func autorecode_trns_proc;
-static trns_free_func autorecode_trns_free;
+static const struct trns_class autorecode_trns_class;
 
 static int compare_arc_items (const void *, const void *, const void *aux);
 static void arc_free (struct autorecode_pgm *);
@@ -450,7 +449,7 @@ cmd_autorecode (struct lexer *lexer, struct dataset *ds)
       /* Free array. */
       free (items);
     }
-  add_transformation (ds, autorecode_trns_proc, autorecode_trns_free, arc);
+  add_transformation (ds, &autorecode_trns_class, arc);
 
   for (size_t i = 0; i < n_dsts; i++)
     free (dst_names[i]);
@@ -553,7 +552,7 @@ compare_arc_items (const void *a_, const void *b_, const void *direction_)
   return direction == ASCENDING ? cmp : -cmp;
 }
 
-static int
+static enum trns_result
 autorecode_trns_proc (void *arc_, struct ccase **c,
                       casenumber case_idx UNUSED)
 {
@@ -582,3 +581,9 @@ autorecode_trns_free (void *arc_)
   arc_free (arc);
   return true;
 }
+
+static const struct trns_class autorecode_trns_class = {
+  .name = "AUTORECODE",
+  .execute = autorecode_trns_proc,
+  .destroy = autorecode_trns_free,
+};
index 819e836d0ef23cc45bc9aecc509271d55f7a1004..95c57740b2a25110c2d3ed3084b77634a692eda0 100644 (file)
@@ -615,7 +615,7 @@ descriptives_set_all_sysmis_zscores (const struct dsc_trns *t, struct ccase *c)
    analyis. 4) any of the variables in the original analysis were missing
    (either system or user-missing values that weren't included).
 */
-static int
+static enum trns_result
 descriptives_trns_proc (void *trns_, struct ccase **c,
                         casenumber case_idx UNUSED)
 {
@@ -711,6 +711,12 @@ descriptives_trns_free (void *trns_)
   return ok;
 }
 
+static const struct trns_class descriptives_trns_class = {
+  .name = "DESCRIPTIVES (Z scores)",
+  .execute = descriptives_trns_proc,
+  .destroy = descriptives_trns_free,
+};
+
 /* Sets up a transformation to calculate Z scores. */
 static void
 setup_z_trns (struct dsc_proc *dsc, struct dataset *ds)
@@ -766,8 +772,7 @@ setup_z_trns (struct dsc_proc *dsc, struct dataset *ds)
        }
     }
 
-  add_transformation (ds,
-                     descriptives_trns_proc, descriptives_trns_free, t);
+  add_transformation (ds, &descriptives_trns_class, t);
 }
 \f
 /* Statistical calculation. */
index e3bb76e42257362387c0077c2f4fb7f3a047ab64..be04af929fb1dc435a090f481965d359b054ab20 100644 (file)
@@ -610,7 +610,7 @@ quick_cluster_show_centers (struct Kmeans *kmeans, bool initial, const struct qc
 /* A transformation function which juxtaposes the dataset with the
    (pre-prepared) dataset containing membership and/or distance
    values.  */
-static int
+static enum trns_result
 save_trans_func (void *aux, struct ccase **c, casenumber x UNUSED)
 {
   const struct save_trans_data *std = aux;
@@ -1076,7 +1076,12 @@ cmd_quick_cluster (struct lexer *lexer, struct dataset *ds)
          std->distance = dict_create_var_assert (qc.dict, qc.var_distance, 0);
        }
 
-      add_transformation (qc.dataset, save_trans_func, save_trans_destroy, std);
+      static const struct trns_class trns_class = {
+        .name = "QUICK CLUSTER",
+        .execute = save_trans_func,
+        .destroy = save_trans_destroy,
+      };
+      add_transformation (qc.dataset, &trns_class, std);
     }
 
   free (qc.var_distance);
index ecf9a65914bd6403ceb3defd673463b3dd647cff..7e76d68abac4d74b8b858b83254966976c9af550 100644 (file)
@@ -912,7 +912,7 @@ advance_ranking (struct rank_trns_input_var *iv)
   iv->current = casereader_read (iv->input);
 }
 
-static int
+static enum trns_result
 rank_trns_proc (void *trns_, struct ccase **c, casenumber case_idx UNUSED)
 {
   struct rank_trns *trns = trns_;
@@ -961,6 +961,12 @@ rank_trns_free (void *trns_)
   return true;
 }
 
+static const struct trns_class rank_trns_class = {
+  .name = "RANK",
+  .execute = rank_trns_proc,
+  .destroy = rank_trns_free,
+};
+
 static bool
 rank_cmd (struct dataset *ds, const struct rank *cmd)
 {
@@ -1116,7 +1122,7 @@ rank_cmd (struct dataset *ds, const struct rank *cmd)
     }
   free (outputs);
 
-  add_transformation (ds, rank_trns_proc, rank_trns_free, trns);
+  add_transformation (ds, &rank_trns_class, trns);
 
   /* Delete our sort key, which we don't need anymore. */
   dict_delete_var (d, order_var);
index e08f4a1d76f051b4686f3d4a1b19cd00cee725f7..b4448dd9e58ead03cb61cf3d5e5b3c97201b1edf 100644 (file)
@@ -159,7 +159,7 @@ save_trans_free (void *aux)
   return true;
 }
 
-static int
+static enum trns_result
 save_trans_func (void *aux, struct ccase **c, casenumber x UNUSED)
 {
   struct save_trans_data *save_trans_data = aux;
@@ -191,7 +191,6 @@ save_trans_func (void *aux, struct ccase **c, casenumber x UNUSED)
   return TRNS_CONTINUE;
 }
 
-
 int
 cmd_regression (struct lexer *lexer, struct dataset *ds)
 {
@@ -450,7 +449,12 @@ cmd_regression (struct lexer *lexer, struct dataset *ds)
       memcpy (save_trans_data->ws, &workspace, sizeof (workspace));
       save_trans_data->n_dep_vars = regression.n_dep_vars;
 
-      add_transformation (ds, save_trans_func, save_trans_free, save_trans_data);
+      static const struct trns_class trns_class = {
+        .name = "REGRESSION",
+        .execute = save_trans_func,
+        .destroy = save_trans_free,
+      };
+      add_transformation (ds, &trns_class, save_trans_data);
     }
 
 
index 9193dbc71e9c0f057efc911c12dfbb2c3462eb38..5c330f65a823e746a8dfefa854f79ff142529e56 100644 (file)
@@ -85,8 +85,8 @@ static struct expression *parse_rvalue (struct lexer *lexer,
                                        struct dataset *);
 
 static struct compute_trns *compute_trns_create (void);
-static trns_proc_func *get_proc_func (const struct lvalue *);
-static trns_free_func compute_trns_free;
+static bool compute_trns_free (void *compute_);
+static const struct trns_class *get_trns_class (const struct lvalue *);
 \f
 /* COMPUTE. */
 
@@ -109,7 +109,7 @@ cmd_compute (struct lexer *lexer, struct dataset *ds)
   if (compute->rvalue == NULL)
     goto fail;
 
-  add_transformation (ds, get_proc_func (lvalue), compute_trns_free, compute);
+  add_transformation (ds, get_trns_class (lvalue), compute);
 
   lvalue_finalize (lvalue, compute, dict);
 
@@ -124,7 +124,7 @@ cmd_compute (struct lexer *lexer, struct dataset *ds)
 /* Transformation functions. */
 
 /* Handle COMPUTE or IF with numeric target variable. */
-static int
+static enum trns_result
 compute_num (void *compute_, struct ccase **c, casenumber case_num)
 {
   struct compute_trns *compute = compute_;
@@ -142,7 +142,7 @@ compute_num (void *compute_, struct ccase **c, casenumber case_num)
 
 /* Handle COMPUTE or IF with numeric vector element target
    variable. */
-static int
+static enum trns_result
 compute_num_vec (void *compute_, struct ccase **c, casenumber case_num)
 {
   struct compute_trns *compute = compute_;
@@ -178,7 +178,7 @@ compute_num_vec (void *compute_, struct ccase **c, casenumber case_num)
 }
 
 /* Handle COMPUTE or IF with string target variable. */
-static int
+static enum trns_result
 compute_str (void *compute_, struct ccase **c, casenumber case_num)
 {
   struct compute_trns *compute = compute_;
@@ -198,7 +198,7 @@ compute_str (void *compute_, struct ccase **c, casenumber case_num)
 
 /* Handle COMPUTE or IF with string vector element target
    variable. */
-static int
+static enum trns_result
 compute_str_vec (void *compute_, struct ccase **c, casenumber case_num)
 {
   struct compute_trns *compute = compute_;
@@ -249,7 +249,7 @@ cmd_if (struct lexer *lexer, struct dataset *ds)
   compute = compute_trns_create ();
 
   /* Test expression. */
-  compute->test = expr_parse_bool (lexer, NULL, ds);
+  compute->test = expr_parse_bool (lexer, ds);
   if (compute->test == NULL)
     goto fail;
 
@@ -265,7 +265,7 @@ cmd_if (struct lexer *lexer, struct dataset *ds)
   if (compute->rvalue == NULL)
     goto fail;
 
-  add_transformation (ds, get_proc_func (lvalue), compute_trns_free, compute);
+  add_transformation (ds, get_trns_class (lvalue), compute);
 
   lvalue_finalize (lvalue, compute, dict);
 
@@ -279,15 +279,35 @@ cmd_if (struct lexer *lexer, struct dataset *ds)
 \f
 /* Code common to COMPUTE and IF. */
 
-static trns_proc_func *
-get_proc_func (const struct lvalue *lvalue)
+static const struct trns_class *
+get_trns_class (const struct lvalue *lvalue)
 {
+  static const struct trns_class classes[2][2] = {
+    [false][false] = {
+      .name = "COMPUTE",
+      .execute = compute_str,
+      .destroy = compute_trns_free
+    },
+    [false][true] = {
+      .name = "COMPUTE",
+      .execute = compute_str_vec,
+      .destroy = compute_trns_free
+    },
+    [true][false] = {
+      .name = "COMPUTE",
+      .execute = compute_num,
+      .destroy = compute_trns_free
+    },
+    [true][true] = {
+      .name = "COMPUTE",
+      .execute = compute_num_vec,
+      .destroy = compute_trns_free
+    },
+  };
+
   bool is_numeric = lvalue_get_type (lvalue) == VAL_NUMERIC;
   bool is_vector = lvalue_is_vector (lvalue);
-
-  return (is_numeric
-          ? (is_vector ? compute_num_vec : compute_num)
-          : (is_vector ? compute_str_vec : compute_str));
+  return &classes[is_numeric][is_vector];
 }
 
 /* Parses and returns an rvalue expression of the same type as
@@ -297,9 +317,9 @@ parse_rvalue (struct lexer *lexer,
              const struct lvalue *lvalue, struct dataset *ds)
 {
   if (lvalue->is_new_variable)
-    return expr_parse_new_variable (lexer, NULL, ds, var_get_name (lvalue->variable));
+    return expr_parse_new_variable (lexer, ds, var_get_name (lvalue->variable));
   else
-    return expr_parse (lexer, NULL, ds, lvalue_get_type (lvalue));
+    return expr_parse (lexer, ds, lvalue_get_type (lvalue));
 }
 
 /* Returns a new struct compute_trns after initializing its fields. */
@@ -362,7 +382,7 @@ lvalue_parse (struct lexer *lexer, struct dataset *ds)
       lex_get (lexer);
       if (!lex_force_match (lexer, T_LPAREN))
        goto lossage;
-      lvalue->element = expr_parse (lexer, NULL, ds, VAL_NUMERIC);
+      lvalue->element = expr_parse (lexer, ds, VAL_NUMERIC);
       if (lvalue->element == NULL)
         goto lossage;
       if (!lex_force_match (lexer, T_RPAREN))
index 0369af1e5d8687071edd44350c45c89760c42d75..1a1422b31f0a69ff695abe4cc7e76888957d0ce4 100644 (file)
@@ -88,13 +88,13 @@ struct count_trns
     struct pool *pool;
   };
 
-static trns_proc_func count_trns_proc;
-static trns_free_func count_trns_free;
+static const struct trns_class count_trns_class;
 
 static bool parse_numeric_criteria (struct lexer *, struct pool *, struct criteria *);
 static bool parse_string_criteria (struct lexer *, struct pool *,
                                    struct criteria *,
                                    const char *dict_encoding);
+static bool count_trns_free (void *trns_);
 \f
 int
 cmd_count (struct lexer *lexer, struct dataset *ds)
@@ -185,7 +185,7 @@ cmd_count (struct lexer *lexer, struct dataset *ds)
           dv->var = dict_create_var_assert (dataset_dict (ds), dv->name, 0);
       }
 
-  add_transformation (ds, count_trns_proc, count_trns_free, trns);
+  add_transformation (ds, &count_trns_class, trns);
   return CMD_SUCCESS;
 
 fail:
@@ -337,7 +337,7 @@ count_string (struct criteria *crit, const struct ccase *c)
 }
 
 /* Performs the COUNT transformation T on case C. */
-static int
+static enum trns_result
 count_trns_proc (void *trns_, struct ccase **c,
                  casenumber case_num UNUSED)
 {
@@ -369,3 +369,9 @@ count_trns_free (void *trns_)
   pool_destroy (trns->pool);
   return true;
 }
+
+static const struct trns_class count_trns_class = {
+  .name = "COUNT",
+  .execute = count_trns_proc,
+  .destroy = count_trns_free,
+};
index f23fcec3f1fc79c6b8511e34f54b9e60a56ecaf1..f90b19673ebc6d8bf2d9d820dd9498e1678f839f 100644 (file)
 #include "language/command.h"
 #include "language/lexer/lexer.h"
 #include "libpspp/message.h"
-
-static int trns_fail (void *x, struct ccase **c, casenumber n);
 \f
 /* A transformation which is guaranteed to fail. */
 
-static int
+static enum trns_result
 trns_fail (void *x UNUSED, struct ccase **c UNUSED,
           casenumber n UNUSED)
 {
@@ -40,6 +38,10 @@ trns_fail (void *x UNUSED, struct ccase **c UNUSED,
 int
 cmd_debug_xform_fail (struct lexer *lexer UNUSED, struct dataset *ds)
 {
-  add_transformation (ds, trns_fail, NULL, NULL);
+  static const struct trns_class fail_trns_class = {
+    .name = "DEBUG XFORM FAIL",
+    .execute = trns_fail
+  };
+  add_transformation (ds, &fail_trns_class, NULL);
   return CMD_SUCCESS;
 }
index a2d963188ac949d64fa5e1ea9829d8f59375411a..4d491dd9be1462f49d46a0f0054d50e1f255d2d7 100644 (file)
@@ -128,8 +128,9 @@ static void set_map_out_str (struct map_out *, struct pool *,
 static bool enlarge_dst_widths (struct recode_trns *);
 static void create_dst_vars (struct recode_trns *, struct dictionary *);
 
-static trns_proc_func recode_trns_proc;
-static trns_free_func recode_trns_free;
+static bool recode_trns_free (void *trns_);
+
+static const struct trns_class recode_trns_class;
 \f
 /* Parser. */
 
@@ -173,8 +174,7 @@ cmd_recode (struct lexer *lexer, struct dataset *ds)
        create_dst_vars (trns, dict);
 
       /* Done. */
-      add_transformation (ds,
-                         recode_trns_proc, recode_trns_free, trns);
+      add_transformation (ds, &recode_trns_class, trns);
     }
   while (lex_match (lexer, T_SLASH));
 
@@ -690,7 +690,7 @@ find_src_string (struct recode_trns *trns, const uint8_t *value,
 }
 
 /* Performs RECODE transformation. */
-static int
+static enum trns_result
 recode_trns_proc (void *trns_, struct ccase **c, casenumber case_idx UNUSED)
 {
   struct recode_trns *trns = trns_;
@@ -747,3 +747,9 @@ recode_trns_free (void *trns_)
   pool_destroy (trns->pool);
   return true;
 }
+
+static const struct trns_class recode_trns_class = {
+  .name = "RECODE",
+  .execute = recode_trns_proc,
+  .destroy = recode_trns_free,
+};
index 7184562ef2943fd060357450adee5f600658c529..665cd4468edc370e80390441cb27d1ddb9fa3f27 100644 (file)
@@ -51,8 +51,7 @@ struct sample_trns
     unsigned frac;              /* TYPE_FRACTION: a fraction of UINT_MAX. */
   };
 
-static trns_proc_func sample_trns_proc;
-static trns_free_func sample_trns_free;
+static const struct trns_class sample_trns_class;
 
 int
 cmd_sample (struct lexer *lexer, struct dataset *ds)
@@ -101,13 +100,13 @@ cmd_sample (struct lexer *lexer, struct dataset *ds)
   trns->N = b;
   trns->m = trns->t = 0;
   trns->frac = frac;
-  add_transformation (ds, sample_trns_proc, sample_trns_free, trns);
+  add_transformation (ds, &sample_trns_class, trns);
 
   return CMD_SUCCESS;
 }
 
 /* Executes a SAMPLE transformation. */
-static int
+static enum trns_result
 sample_trns_proc (void *t_, struct ccase **c UNUSED,
                   casenumber case_num UNUSED)
 {
@@ -146,3 +145,9 @@ sample_trns_free (void *t_)
   free (t);
   return true;
 }
+
+static const struct trns_class sample_trns_class = {
+  .name = "SAMPLE",
+  .execute = sample_trns_proc,
+  .destroy = sample_trns_free,
+};
index 72284f36539ca14d658c3298612b0e8d13ca0034..deb13047e9fd7fcc49a18629e3ccb32d4417ebea 100644 (file)
@@ -40,8 +40,7 @@ struct select_if_trns
     struct expression *e;      /* Test expression. */
   };
 
-static trns_proc_func select_if_proc;
-static trns_free_func select_if_free;
+static const struct trns_class select_if_trns_class;
 
 /* Parses the SELECT IF transformation. */
 int
@@ -50,7 +49,7 @@ cmd_select_if (struct lexer *lexer, struct dataset *ds)
   struct expression *e;
   struct select_if_trns *t;
 
-  e = expr_parse_bool (lexer, NULL, ds);
+  e = expr_parse_bool (lexer, ds);
   if (!e)
     return CMD_CASCADING_FAILURE;
 
@@ -63,13 +62,13 @@ cmd_select_if (struct lexer *lexer, struct dataset *ds)
 
   t = xmalloc (sizeof *t);
   t->e = e;
-  add_transformation (ds, select_if_proc, select_if_free, t);
+  add_transformation (ds, &select_if_trns_class, t);
 
   return CMD_SUCCESS;
 }
 
 /* Performs the SELECT IF transformation T on case C. */
-static int
+static enum trns_result
 select_if_proc (void *t_, struct ccase **c,
                 casenumber case_num)
 {
@@ -88,6 +87,12 @@ select_if_free (void *t_)
   return true;
 }
 
+static const struct trns_class select_if_trns_class = {
+  .name = "SELECT IF",
+  .execute = select_if_proc,
+  .destroy = select_if_free,
+};
+
 /* Parses the FILTER command. */
 int
 cmd_filter (struct lexer *lexer, struct dataset *ds)
index d0bf071765c8ba9d327c39b109df1c928f87be98..1a1889bb97ff65ab5863d9af7bb41059b4846b34 100644 (file)
@@ -65,34 +65,61 @@ AT_CHECK([pspp do-if.sps], [0], [ignore])
 AT_CHECK([cat do-if.out], [0], [expout])
 AT_CLEANUP
 
-AT_SETUP([unpaired END IF crash])
+AT_SETUP([DO IF - negative])
 AT_DATA([do-if.sps], [dnl
 DATA LIST LIST NOTABLE/a b c.
-END IF.
-])
-AT_CHECK([pspp -O format=csv do-if.sps], [1], [dnl
-do-if.sps:2: error: END IF: This command cannot appear outside DO IF...END IF.
-])
-AT_CLEANUP
+BEGIN DATA.
+1 2 3
+END DATA.
 
+END IF.
+ELSE.
+ELSE IF 1.
 
+DO IF 0.
+ELSE.
+ELSE.
+END IF.
 
-AT_SETUP([ELSE without DO IF])
-AT_DATA([do-if.sps], [dnl
-DATA lIST NOTABLE LIST /QUA BRA *.
-BEGIN DATA
-4  1
-6  3
-END DATA
+DO IF 0.
+ELSE.
+ELSE IF 0.
+END IF.
 
-ELSE QUA 'A string'.
+DO IF !.
+END IF.
 
-EXECUTE.
+DO IF 0.
 ])
 AT_CHECK([pspp -O format=csv do-if.sps], [1], [dnl
+do-if.sps:6: error: END IF: This command cannot appear outside DO IF...END IF.
+
 do-if.sps:7: error: ELSE: This command cannot appear outside DO IF...END IF.
 
-do-if.sps:9: error: Stopping syntax file processing here to avoid a cascade of dependent command failures.
+do-if.sps:8: error: ELSE IF: This command cannot appear outside DO IF...END IF.
+
+do-if.sps:12: error: DO IF: Only one ELSE is allowed within DO IF...END IF.
+
+"do-if.sps:11.1-11.5: note: DO IF: This is the location of the previous ELSE clause.
+   11 | ELSE.
+      | ^~~~~"
+
+"do-if.sps:10.1-10.8: note: DO IF: This is the location of the DO IF command.
+   10 | DO IF 0.
+      | ^~~~~~~~"
+
+do-if.sps:17: error: DO IF: ELSE IF is not allowed following ELSE within DO IF...END IF.
+
+"do-if.sps:16.1-16.5: note: DO IF: This is the location of the previous ELSE clause.
+   16 | ELSE.
+      | ^~~~~"
+
+"do-if.sps:15.1-15.8: note: DO IF: This is the location of the DO IF command.
+   15 | DO IF 0.
+      | ^~~~~~~~"
+
+do-if.sps:20.7: error: DO IF: Syntax error at `!'.
+
+error: DO IF: Syntax error at end of input.
 ])
 AT_CLEANUP
-
index a2ceac3de350ef79cc899bd4fcb2c0af7546177a..d8ed2878854904b97ddfacde469bbb9ed479486d 100644 (file)
@@ -260,3 +260,82 @@ AT_CHECK([cat pspp.csv], [0], [dnl
 --------
 ])
 AT_CLEANUP
+
+AT_SETUP([LOOP negative])
+AT_DATA([loop.sps], [dnl
+DATA LIST NOTABLE /x 1.
+BREAK.
+END LOOP.
+
+LOOP A=1 TO 5 B=1 TO 5.
+END LOOP.
+LOOP 5.
+END LOOP.
+LOOP B !.
+END LOOP.
+LOOP B=!.
+END LOOP.
+LOOP A=1 TO !.
+END LOOP.
+LOOP A=1 BY !.
+END LOOP.
+LOOP A=1 TO 5 BY 5 TO !.
+END LOOP.
+LOOP A=1 BY 5 TO 10 BY !.
+END LOOP.
+LOOP A=1.
+END LOOP.
+LOOP !.
+END LOOP.
+
+LOOP IF 1 IF 0.
+END LOOP.
+
+LOOP IF !.
+END LOOP.
+
+LOOP.
+END LOOP IF !.
+
+LOOP.
+END LOOP !.
+
+LOOP.
+])
+AT_CHECK([pspp loop.sps], 1, [dnl
+loop.sps:2: error: BREAK: This command cannot appear outside LOOP...END LOOP.
+
+loop.sps:3: error: END LOOP: This command cannot appear outside LOOP...END
+LOOP.
+
+loop.sps:5: error: LOOP: Only one index clause may be specified.
+
+loop.sps:7.6: error: LOOP: Syntax error at `5'.
+
+loop.sps:9.8: error: LOOP: Syntax error at `!': expecting `='.
+
+loop.sps:11.8: error: LOOP: Syntax error at `!'.
+
+loop.sps:13.13: error: LOOP: Syntax error at `!'.
+
+loop.sps:15.13: error: LOOP: Syntax error at `!'.
+
+loop.sps:17: error: LOOP: Subcommand TO may only be specified once.
+
+loop.sps:19: error: LOOP: Subcommand BY may only be specified once.
+
+loop.sps:21: error: LOOP: Required subcommand TO was not specified.
+
+loop.sps:23.6: error: LOOP: Syntax error at `!'.
+
+loop.sps:26: error: LOOP: Subcommand IF may only be specified once.
+
+loop.sps:29.9: error: LOOP: Syntax error at `!'.
+
+loop.sps:33.13: error: LOOP: Syntax error at `!'.
+
+loop.sps:36.10: error: LOOP: Syntax error at `!': expecting end of command.
+
+error: LOOP: Syntax error at end of input.
+])
+AT_CLEANUP
\ No newline at end of file
index f676e308bb5b72b91040201d6395675513b015c9..0e93ed654a042cc5795acfd827ea5d659b126c6f 100644 (file)
@@ -96,3 +96,249 @@ vec1,vec2,vec3,vec4,vec5
 1.00,2.00,3.00,4.00,5.00
 ])
 AT_CLEANUP
+
+AT_SETUP([INPUT PROGRAM taking shorter of two files])
+AT_DATA([input-program.sps], [dnl
+INPUT PROGRAM.
+    DATA LIST NOTABLE FILE='a.txt'/X 1-10.
+    DATA LIST NOTABLE FILE='b.txt'/Y 1-10.
+END INPUT PROGRAM.
+LIST.
+])
+AT_DATA([short.txt], [dnl
+1
+2
+3
+])
+AT_DATA([long.txt], [dnl
+4
+5
+6
+7
+])
+
+cp short.txt a.txt
+cp long.txt b.txt
+AT_CHECK([pspp -O format=csv input-program.sps], 0, [dnl
+Table: Data List
+X,Y
+1,4
+2,5
+3,6
+])
+
+cp short.txt b.txt
+cp long.txt a.txt
+AT_CHECK([pspp -O format=csv input-program.sps], 0, [dnl
+Table: Data List
+X,Y
+4,1
+5,2
+6,3
+])
+AT_CLEANUP
+
+AT_SETUP([INPUT PROGRAM taking longer of two files])
+AT_DATA([input-program.sps], [dnl
+INPUT PROGRAM.
+    NUMERIC #A #B.
+
+    DO IF NOT #A.
+        DATA LIST NOTABLE END=#A FILE='a.txt'/X 1-10.
+    END IF.
+    DO IF NOT #B.
+        DATA LIST NOTABLE END=#B FILE='b.txt'/Y 1-10.
+    END IF.
+    DO IF #A AND #B.
+        END FILE.
+    END IF.
+    END CASE.
+END INPUT PROGRAM.
+LIST.
+])
+AT_DATA([short.txt], [dnl
+1
+2
+3
+])
+AT_DATA([long.txt], [dnl
+4
+5
+6
+7
+8
+])
+
+cp short.txt a.txt
+cp long.txt b.txt
+AT_CHECK([pspp -O format=csv input-program.sps], 0, [dnl
+Table: Data List
+X,Y
+1,4
+2,5
+3,6
+.,7
+.,8
+])
+
+cp short.txt b.txt
+cp long.txt a.txt
+AT_CHECK([pspp -O format=csv input-program.sps], 0, [dnl
+Table: Data List
+X,Y
+4,1
+5,2
+6,3
+7,.
+8,.
+])
+AT_CLEANUP
+
+AT_SETUP([INPUT PROGRAM concatenating two files - version 1])
+AT_DATA([input-program.sps], [dnl
+INPUT PROGRAM.
+    NUMERIC #A #B.
+
+    DO IF #A.
+        DATA LIST NOTABLE END=#B FILE='b.txt'/X 1-10.
+        DO IF #B.
+            END FILE.
+        ELSE.
+            END CASE.
+        END IF.
+    ELSE.
+        DATA LIST NOTABLE END=#A FILE='a.txt'/X 1-10.
+        DO IF NOT #A.
+            END CASE.
+        END IF.
+    END IF.
+END INPUT PROGRAM.
+LIST.
+])
+AT_DATA([a.txt], [dnl
+1
+2
+3
+])
+AT_DATA([b.txt], [dnl
+4
+5
+6
+7
+8
+])
+
+AT_CHECK([pspp -O format=csv input-program.sps], 0, [dnl
+Table: Data List
+X
+1
+2
+3
+4
+5
+6
+7
+8
+])
+AT_CLEANUP
+
+AT_SETUP([INPUT PROGRAM concatenating two files - version 2])
+AT_DATA([input-program.sps], [dnl
+INPUT PROGRAM.
+    NUMERIC #EOF.
+
+    LOOP IF NOT #EOF.
+        DATA LIST NOTABLE END=#EOF FILE='a.txt'/X 1-10.
+        DO IF NOT #EOF.
+            END CASE.
+        END IF.
+    END LOOP.
+
+    COMPUTE #EOF = 0.
+    LOOP IF NOT #EOF.
+        DATA LIST NOTABLE END=#EOF FILE='b.txt'/X 1-10.
+        DO IF NOT #EOF.
+            END CASE.
+        END IF.
+    END LOOP.
+
+    END FILE.
+END INPUT PROGRAM.
+LIST.
+])
+AT_DATA([a.txt], [dnl
+1
+2
+3
+])
+AT_DATA([b.txt], [dnl
+4
+5
+6
+7
+8
+])
+
+AT_CHECK([pspp -O format=csv input-program.sps], 0, [dnl
+Table: Data List
+X
+1
+2
+3
+4
+5
+6
+7
+8
+])
+AT_CLEANUP
+
+AT_SETUP([INPUT PROGRAM generating data])
+AT_DATA([input-program.sps], [dnl
+INPUT PROGRAM.
+    LOOP #I=1 TO 10.
+        COMPUTE X=#I.
+        END CASE.
+    END LOOP.
+    END FILE.
+END INPUT PROGRAM.
+FORMAT X(F2).
+LIST.
+])
+AT_CHECK([pspp -O format=csv input-program.sps], 0, [dnl
+Table: Data List
+X
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+])
+AT_CLEANUP
+
+AT_SETUP([INPUT PROGRAM unexpected end of file])
+AT_DATA([input-program.sps], [dnl
+INPUT PROGRAM.
+])
+AT_CHECK([pspp input-program.sps], 1, [dnl
+error: INPUT PROGRAM: Unexpected end-of-file within INPUT PROGRAM.
+])
+AT_CLEANUP
+
+
+AT_SETUP([INPUT PROGRAM no variables])
+AT_DATA([input-program.sps], [dnl
+INPUT PROGRAM.
+END FILE.
+END INPUT PROGRAM.
+])
+AT_CHECK([pspp input-program.sps], 1, [dnl
+input-program.sps:3: error: INPUT PROGRAM: Input program did not create any
+variables.
+])
+AT_CLEANUP
\ No newline at end of file