@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
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;
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;
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);
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);
{
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;
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. */
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;
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;
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;
}
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.
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);
}
}
{
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;
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
{
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_;
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;
}
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)
{
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)
? 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)
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 *);
\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);
#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;
#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 */
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:
/* 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. */
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. */
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:
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. */
/* 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)
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)
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)
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. */
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;
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 \
+++ /dev/null
-/*
-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;
-}
+++ /dev/null
-/* 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 */
#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,
+};
#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. */
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)
{
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)
{
}
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;
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;
}
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
*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. */
{
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,
+};
#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"
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)
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);
fh_unref (fh);
free (encoding);
- return CMD_DATA_LIST;
+ data_list_seen ();
+
+ return CMD_SUCCESS;
error:
data_parser_destroy (parser);
}
/* 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))
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,
+};
{
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. */
};
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. */
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)
{
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));
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. */
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
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);
/* 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);
}
};
\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
{
goto error;
}
- e = expr_parse (lexer, NULL, ds, VAL_NUMERIC);
+ e = expr_parse (lexer, ds, VAL_NUMERIC);
if (!e)
goto error;
}
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);
}
/* 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_;
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,
+};
#include <stdbool.h>
bool in_input_program (void);
-void cancel_input_program (void);
+
+void data_list_seen (void);
#endif /* inpt-pgm.h */
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)
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"));
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;
}
/* Executes a PRINT SPACE transformation. */
-static int
+static enum trns_result
print_space_trns_proc (void *t_, struct ccase **c,
casenumber case_num UNUSED)
{
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,
+};
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. */
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);
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)
{
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)
{
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,
+};
+
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,
return CMD_CASCADING_FAILURE;
}
- add_transformation (ds, output_trns_proc, output_trns_free, t);
+ add_transformation (ds, &output_trns_class, t);
return CMD_SUCCESS;
}
}
/* 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_;
free (t);
return ok;
}
+
+static const struct trns_class output_trns_class = {
+ .name = "XSAVE/XEXPORT",
+ .execute = output_trns_proc,
+ .destroy = output_trns_free,
+};
/* 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));
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);
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);
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. */
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 *);
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 *);
/* 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]);
return direction == ASCENDING ? cmp : -cmp;
}
-static int
+static enum trns_result
autorecode_trns_proc (void *arc_, struct ccase **c,
casenumber case_idx UNUSED)
{
arc_free (arc);
return true;
}
+
+static const struct trns_class autorecode_trns_class = {
+ .name = "AUTORECODE",
+ .execute = autorecode_trns_proc,
+ .destroy = autorecode_trns_free,
+};
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)
{
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)
}
}
- add_transformation (ds,
- descriptives_trns_proc, descriptives_trns_free, t);
+ add_transformation (ds, &descriptives_trns_class, t);
}
\f
/* Statistical calculation. */
/* 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;
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);
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_;
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)
{
}
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);
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;
return TRNS_CONTINUE;
}
-
int
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);
}
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. */
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);
/* 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_;
/* 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_;
}
/* 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_;
/* 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_;
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;
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);
\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
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. */
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))
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)
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:
}
/* 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)
{
pool_destroy (trns->pool);
return true;
}
+
+static const struct trns_class count_trns_class = {
+ .name = "COUNT",
+ .execute = count_trns_proc,
+ .destroy = count_trns_free,
+};
#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)
{
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;
}
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. */
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));
}
/* 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_;
pool_destroy (trns->pool);
return true;
}
+
+static const struct trns_class recode_trns_class = {
+ .name = "RECODE",
+ .execute = recode_trns_proc,
+ .destroy = recode_trns_free,
+};
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)
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)
{
free (t);
return true;
}
+
+static const struct trns_class sample_trns_class = {
+ .name = "SAMPLE",
+ .execute = sample_trns_proc,
+ .destroy = sample_trns_free,
+};
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
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;
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)
{
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)
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
-
--------
])
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
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