From 077a1c38bd58911cb74a08f95be3691e49b87779 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 9 Feb 2007 05:28:21 +0000 Subject: [PATCH] Improve VECTOR implementation. Bug #18706. --- doc/variables.texi | 24 ++- src/data/ChangeLog | 4 + src/data/dictionary.c | 12 ++ src/data/dictionary.h | 3 + src/language/dictionary/ChangeLog | 10 ++ src/language/dictionary/sys-file-info.c | 56 +++++-- src/language/dictionary/vector.c | 212 ++++++++++++------------ src/output/ChangeLog | 5 + src/output/table.c | 2 +- tests/ChangeLog | 6 + tests/automake.mk | 1 + tests/command/vector.sh | 152 +++++++++++++++++ 12 files changed, 349 insertions(+), 138 deletions(-) create mode 100755 tests/command/vector.sh diff --git a/doc/variables.texi b/doc/variables.texi index c2952fbd..1436158e 100644 --- a/doc/variables.texi +++ b/doc/variables.texi @@ -447,25 +447,26 @@ Currently, this has no effect except for certain third party software. @display Two possible syntaxes: VECTOR vec_name=var_list. - VECTOR vec_name_list(count). + VECTOR vec_name_list(count [format]). @end display @cmd{VECTOR} allows a group of variables to be accessed as if they were consecutive members of an array with a vector(index) notation. -To make a vector out of a set of existing variables, specify a name for -the vector followed by an equals sign (@samp{=}) and the variables that -belong in the vector. +To make a vector out of a set of existing variables, specify a name +for the vector followed by an equals sign (@samp{=}) and the variables +to put in the vector. All the variables in the vector must be the same +type. String variables in a vector must all have the same width. To make a vector and create variables at the same time, specify one or more vector names followed by a count in parentheses. This will cause variables named @code{@var{vec}1} through @code{@var{vec}@var{count}} -to be created as numeric variables with print and write format F8.2. -Variable names including numeric suffixes may not exceed 64 characters -in length, and none of the variables may exist prior to @cmd{VECTOR}. - -All the variables in a vector must be the same type. String variables -in a vector must all have the same width. +to be created as numeric variables. By default, the new variables +have print and write format F8.2, but an alternate format may be +specified inside the parentheses before or after the count and +separated from it by white space or a comma. Variable names including +numeric suffixes may not exceed 64 characters in length, and none of +the variables may exist prior to @cmd{VECTOR}. Vectors created with @cmd{VECTOR} disappear after any procedure or procedure-like command is executed. The variables contained in the @@ -475,9 +476,6 @@ Variables}). Variables within a vector may be referenced in expressions using @code{vector(index)} syntax. - - - @node WRITE FORMATS, , VECTOR, Variable Attributes @section WRITE FORMATS @vindex WRITE FORMATS diff --git a/src/data/ChangeLog b/src/data/ChangeLog index 36351668..67f571db 100644 --- a/src/data/ChangeLog +++ b/src/data/ChangeLog @@ -1,3 +1,7 @@ +Sat Feb 3 21:52:17 2007 Ben Pfaff + + * dictionary.c (dict_create_vector_assert): New function. + Wed Feb 7 21:25:15 2007 Ben Pfaff * file-name.c (fn_normalize): Correct name of function diff --git a/src/data/dictionary.c b/src/data/dictionary.c index 0dadb959..ca1a286c 100644 --- a/src/data/dictionary.c +++ b/src/data/dictionary.c @@ -1137,6 +1137,18 @@ dict_create_vector (struct dictionary *d, return false; } +/* Creates in D a vector named NAME that contains the CNT + variables in VAR. A vector named NAME must not already exist + in D. */ +void +dict_create_vector_assert (struct dictionary *d, + const char *name, + struct variable **var, size_t cnt) +{ + assert (dict_lookup_vector (d, name) == NULL); + dict_create_vector (d, name, var, cnt); +} + /* Returns the vector in D with index IDX, which must be less than dict_get_vector_cnt (D). */ const struct vector * diff --git a/src/data/dictionary.h b/src/data/dictionary.h index 19f466c1..860fdaa6 100644 --- a/src/data/dictionary.h +++ b/src/data/dictionary.h @@ -126,6 +126,9 @@ void dict_set_documents (struct dictionary *, const char *); bool dict_create_vector (struct dictionary *, const char *name, struct variable **, size_t cnt); +void dict_create_vector_assert (struct dictionary *, + const char *name, + struct variable **, size_t cnt); const struct vector *dict_get_vector (const struct dictionary *, size_t idx); size_t dict_get_vector_cnt (const struct dictionary *); diff --git a/src/language/dictionary/ChangeLog b/src/language/dictionary/ChangeLog index 3d7d0baf..bd4c23ae 100644 --- a/src/language/dictionary/ChangeLog +++ b/src/language/dictionary/ChangeLog @@ -1,3 +1,13 @@ +Sat Feb 3 21:52:35 2007 Ben Pfaff + + * vector.c (cmd_vector): Add support for specifying an output + format in the short form of the command, fixing bug #18706. + Rewrite to get rid of weird data structure and simplify. + + * sys-file-info.c (display_vectors): For DISPLAY VECTORS, display, + in addition to the names of vectors, the names, positions, and + print formats of the variables contained in the vectors. + Wed Dec 13 20:59:54 2006 Ben Pfaff * automake.mk: Add delete-variables.c diff --git a/src/language/dictionary/sys-file-info.c b/src/language/dictionary/sys-file-info.c index ebe98015..a13dda37 100644 --- a/src/language/dictionary/sys-file-info.c +++ b/src/language/dictionary/sys-file-info.c @@ -588,6 +588,8 @@ display_vectors (const struct dictionary *dict, int sorted) int i; struct tab_table *t; size_t nvec; + size_t nrow; + size_t row; nvec = dict_get_vector_cnt (dict); if (nvec == 0) @@ -597,32 +599,52 @@ display_vectors (const struct dictionary *dict, int sorted) } vl = xnmalloc (nvec, sizeof *vl); - for (i = 0; i < nvec; i++) - vl[i] = dict_get_vector (dict, i); + nrow = 0; + for (i = 0; i < nvec; i++) + { + vl[i] = dict_get_vector (dict, i); + nrow += vector_get_var_cnt (vl[i]); + } if (sorted) qsort (vl, nvec, sizeof *vl, compare_vector_ptrs_by_name); - t = tab_create (1, nvec + 1, 0); + t = tab_create (4, nrow + 1, 0); tab_headers (t, 0, 0, 1, 0); tab_columns (t, TAB_COL_DOWN, 1); tab_dim (t, tab_natural_dimensions); - tab_hline (t, TAL_1, 0, 0, 1); + tab_box (t, TAL_1, TAL_1, -1, -1, 0, 0, 3, nrow); + tab_box (t, -1, -1, -1, TAL_1, 0, 0, 3, nrow); + tab_hline (t, TAL_2, 0, 3, 1); tab_text (t, 0, 0, TAT_TITLE | TAB_LEFT, _("Vector")); + tab_text (t, 1, 0, TAT_TITLE | TAB_LEFT, _("Position")); + tab_text (t, 2, 0, TAT_TITLE | TAB_LEFT, _("Variable")); + tab_text (t, 3, 0, TAT_TITLE | TAB_LEFT, _("Print Format")); tab_flags (t, SOMF_NO_TITLE); - for (i = 0; i < nvec; i++) - tab_text (t, 0, i + 1, TAB_LEFT, vector_get_name (vl[i])); - tab_submit (t); - - free (vl); -} - - - - - - - + row = 1; + for (i = 0; i < nvec; i++) + { + const struct vector *vec = vl[i]; + size_t j; + + tab_joint_text (t, 0, row, 0, row + vector_get_var_cnt (vec) - 1, + TAB_LEFT, vector_get_name (vl[i])); + for (j = 0; j < vector_get_var_cnt (vec); j++) + { + struct variable *var = vector_get_var (vec, j); + char fmt_string[FMT_STRING_LEN_MAX + 1]; + fmt_to_string (var_get_print_format (var), fmt_string); + + tab_text (t, 1, row, TAB_RIGHT | TAT_PRINTF, "%d", (int) j + 1); + tab_text (t, 2, row, TAB_LEFT, var_get_name (var)); + tab_text (t, 3, row, TAB_LEFT, fmt_string); + row++; + } + tab_hline (t, TAL_1, 0, 3, row); + } + tab_submit (t); + free (vl); +} diff --git a/src/language/dictionary/vector.c b/src/language/dictionary/vector.c index 909daba7..cd50970d 100644 --- a/src/language/dictionary/vector.c +++ b/src/language/dictionary/vector.c @@ -20,73 +20,70 @@ #include +#include #include #include #include #include +#include #include #include #include #include #include #include +#include #include +#include "intprops.h" + #include "gettext.h" #define _(msgid) gettext (msgid) int cmd_vector (struct lexer *lexer, struct dataset *ds) { - /* Just to be different, points to a set of null terminated strings - containing the names of the vectors to be created. The list - itself is terminated by a empty string. So a list of three - elements, A B C, would look like this: "A\0B\0C\0\0". */ - char *vecnames; - - /* vecnames iterators. */ - char *cp, *cp2; - - /* Maximum allocated position for vecnames, plus one position. */ - char *endp = NULL; - struct dictionary *dict = dataset_dict (ds); + struct pool *pool = pool_create (); - cp = vecnames = xmalloc (256); - endp = &vecnames[256]; do { + char **vectors; + size_t vector_cnt, vector_cap; + /* Get the name(s) of the new vector(s). */ if (!lex_force_id (lexer)) return CMD_CASCADING_FAILURE; + + vectors = NULL; + vector_cnt = vector_cap = 0; while (lex_token (lexer) == T_ID) { - if (cp + 16 > endp) + size_t i; + + if (dict_lookup_vector (dict, lex_tokid (lexer))) { - char *old_vecnames = vecnames; - vecnames = xrealloc (vecnames, endp - vecnames + 256); - cp = (cp - old_vecnames) + vecnames; - endp = (endp - old_vecnames) + vecnames + 256; + msg (SE, _("A vector named %s already exists."), + lex_tokid (lexer)); + goto fail; } - for (cp2 = cp; cp2 < cp; cp2 += strlen (cp)) - if (!strcasecmp (cp2, lex_tokid (lexer))) + for (i = 0; i < vector_cnt; i++) + if (!strcasecmp (vectors[i], lex_tokid (lexer))) { - msg (SE, _("Vector name %s is given twice."), lex_tokid (lexer)); + msg (SE, _("Vector name %s is given twice."), + lex_tokid (lexer)); goto fail; } - if (dict_lookup_vector (dict, lex_tokid (lexer))) - { - msg (SE, _("There is already a vector with name %s."), lex_tokid (lexer)); - goto fail; - } + if (vector_cnt == vector_cap) + vectors = pool_2nrealloc (pool, + vectors, &vector_cap, sizeof *vectors); + vectors[vector_cnt++] = xstrdup (lex_tokid (lexer)); - cp = stpcpy (cp, lex_tokid (lexer)) + 1; lex_get (lexer); lex_match (lexer, ','); } - *cp++ = 0; /* Now that we have the names it's time to check for the short or long forms. */ @@ -96,115 +93,116 @@ cmd_vector (struct lexer *lexer, struct dataset *ds) struct variable **v; size_t nv; - if (strchr (vecnames, '\0')[1]) + if (vector_cnt > 1) { - /* There's more than one vector name. */ - msg (SE, _("A slash must be used to separate each vector " - "specification when using the long form. Commands " - "such as VECTOR A,B=Q1 TO Q20 are not supported.")); + msg (SE, _("A slash must separate each vector " + "specification in VECTOR's long form.")); goto fail; } - if (!parse_variables (lexer, dict, &v, &nv, - PV_SAME_WIDTH | PV_DUPLICATE)) + if (!parse_variables_pool (lexer, pool, dict, &v, &nv, + PV_SAME_WIDTH | PV_DUPLICATE)) goto fail; - dict_create_vector (dict, vecnames, v, nv); - free (v); + dict_create_vector (dict, vectors[0], v, nv); } else if (lex_match (lexer, '(')) { - int i; - - /* Maximum number of digits in a number to add to the base - vecname. */ - int ndig; - - /* Name of an individual variable to be created. */ - char name[SHORT_NAME_LEN + 1]; - - /* Vector variables. */ - struct variable **v; - int nv; - - if (!lex_force_int (lexer)) - return CMD_CASCADING_FAILURE; - nv = lex_integer (lexer); - lex_get (lexer); - if (nv <= 0) - { - msg (SE, _("Vectors must have at least one element.")); - goto fail; - } - if (!lex_force_match (lexer, ')')) - goto fail; - - /* First check that all the generated variable names - are LONG_NAME_LEN characters or shorter. */ - ndig = intlog10 (nv); - for (cp = vecnames; *cp;) + /* Short form. */ + struct fmt_spec format; + bool seen_format = false; + + struct variable **vars; + int var_cnt; + + size_t i; + + var_cnt = 0; + format = fmt_for_output (FMT_F, 8, 2); + seen_format = false; + while (!lex_match (lexer, ')')) + { + if (lex_is_integer (lexer) && var_cnt == 0) + { + var_cnt = lex_integer (lexer); + lex_get (lexer); + if (var_cnt <= 0) + { + msg (SE, _("Vectors must have at least one element.")); + goto fail; + } + } + else if (lex_token (lexer) == T_ID && !seen_format) + { + seen_format = true; + if (!parse_format_specifier (lexer, &format) + || !fmt_check_output (&format) + || !fmt_check_type_compat (&format, VAR_NUMERIC)) + goto fail; + } + else + { + lex_error (lexer, NULL); + goto fail; + } + lex_match (lexer, ','); + } + if (var_cnt == 0) + { + lex_error (lexer, _("expecting vector length")); + goto fail; + } + + /* Check that none of the variables exist and that + their names are no more than LONG_NAME_LEN bytes + long. */ + for (i = 0; i < vector_cnt; i++) { - int len = strlen (cp); - if (len + ndig > LONG_NAME_LEN) + int j; + for (j = 0; j < var_cnt; j++) { - msg (SE, _("%s%d is too long for a variable name."), cp, nv); - goto fail; - } - cp += len + 1; - } - - /* Next check that none of the variables exist. */ - for (cp = vecnames; *cp;) - { - for (i = 0; i < nv; i++) - { - sprintf (name, "%s%d", cp, i + 1); - if (dict_lookup_var (dict, name)) + char name[LONG_NAME_LEN + INT_STRLEN_BOUND (int) + 1]; + sprintf (name, "%s%d", vectors[i], j + 1); + if (strlen (name) > LONG_NAME_LEN) + { + msg (SE, _("%s is too long for a variable name."), name); + goto fail; + } + if (dict_lookup_var (dict, name)) { - msg (SE, _("There is already a variable named %s."), - name); + msg (SE, _("%s is an existing variable name."), name); goto fail; } } - cp += strlen (cp) + 1; } /* Finally create the variables and vectors. */ - v = xmalloc (nv * sizeof *v); - for (cp = vecnames; *cp;) + vars = pool_nmalloc (pool, var_cnt, sizeof *vars); + for (i = 0; i < vector_cnt; i++) { - for (i = 0; i < nv; i++) + int j; + for (j = 0; j < var_cnt; j++) { - sprintf (name, "%s%d", cp, i + 1); - v[i] = dict_create_var_assert (dict, name, 0); + char name[LONG_NAME_LEN + 1]; + sprintf (name, "%s%d", vectors[i], j + 1); + vars[j] = dict_create_var_assert (dict, name, 0); + var_set_both_formats (vars[j], &format); } - if (!dict_create_vector (dict, cp, v, nv)) - NOT_REACHED (); - cp += strlen (cp) + 1; + dict_create_vector_assert (dict, vectors[i], vars, var_cnt); } - free (v); } else { - msg (SE, _("The syntax for this command does not match " - "the expected syntax for either the long form " - "or the short form of VECTOR.")); + lex_error (lexer, NULL); goto fail; } - - free (vecnames); - vecnames = NULL; } while (lex_match (lexer, '/')); - if (lex_token (lexer) != '.') - { - lex_error (lexer, _("expecting end of command")); - goto fail; - } - return CMD_SUCCESS; + pool_destroy (pool); + return lex_end_of_command (lexer); fail: - free (vecnames); + pool_destroy (pool); return CMD_FAILURE; } diff --git a/src/output/ChangeLog b/src/output/ChangeLog index 59d7d0e7..336fbd50 100644 --- a/src/output/ChangeLog +++ b/src/output/ChangeLog @@ -1,3 +1,8 @@ +Sat Feb 3 21:56:46 2007 Ben Pfaff + + * table.c (tab_hline): Allow t->nr as y argument, so that we can + draw a line below the bottom row of the table. + Wed Feb 7 21:38:12 2007 Ben Pfaff * afm.c: Add #include . Thanks to John McCabe-Dansted diff --git a/src/output/table.c b/src/output/table.c index a6e9f66f..d488f3aa 100644 --- a/src/output/table.c +++ b/src/output/table.c @@ -274,7 +274,7 @@ tab_hline (struct tab_table * t, int style, int x1, int x2, int y) y += t->row_ofs; assert (y >= 0); - assert (y < t->nr); + assert (y <= t->nr); assert (x2 >= x1 ); assert (x1 >= 0 ); assert (x2 < t->nc); diff --git a/tests/ChangeLog b/tests/ChangeLog index 5b5c8912..d47870f4 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,9 @@ +Sat Feb 3 21:57:34 2007 Ben Pfaff + + * automake.mk: Add tests/command/vector.sh. + + * tests/command/vector.sh: New test. + Wed Jan 24 21:13:53 2007 Ben Pfaff * automake.mk: Add tests/libpspp/abt-test. diff --git a/tests/automake.mk b/tests/automake.mk index c976b898..c6b2337d 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -57,6 +57,7 @@ TESTS = \ tests/command/trimmed-mean.sh \ tests/command/tabs.sh \ tests/command/use.sh \ + tests/command/vector.sh \ tests/command/very-long-strings.sh \ tests/command/weight.sh \ tests/formats/bcd-in.sh \ diff --git a/tests/command/vector.sh b/tests/command/vector.sh new file mode 100755 index 00000000..9e5045bf --- /dev/null +++ b/tests/command/vector.sh @@ -0,0 +1,152 @@ +#!/bin/sh + +# This program tests the VECTOR command + +TEMPDIR=/tmp/pspp-tst-$$ +TESTFILE=$TEMPDIR/`basename $0`.sps + +# ensure that top_builddir are absolute +if [ -z "$top_builddir" ] ; then top_builddir=. ; fi +if [ -z "$top_srcdir" ] ; then top_srcdir=. ; fi +top_builddir=`cd $top_builddir; pwd` +PSPP=$top_builddir/src/ui/terminal/pspp + +# ensure that top_srcdir is absolute +top_srcdir=`cd $top_srcdir; pwd` + +STAT_CONFIG_PATH=$top_srcdir/config +export STAT_CONFIG_PATH + +LANG=C +export LANG + + +cleanup() +{ + cd / + rm -rf $TEMPDIR +} + + +fail() +{ + echo $activity + echo FAILED + cleanup; + exit 1; +} + + +no_result() +{ + echo $activity + echo NO RESULT; + cleanup; + exit 2; +} + +pass() +{ + cleanup; + exit 0; +} + +mkdir -p $TEMPDIR + +cd $TEMPDIR + +activity="create prog" +cat > $TEMPDIR/vector.stat <