From 47bd6f9b29268bddee3a19ae6d013b33c978a7e2 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sat, 5 Nov 2005 03:45:28 +0000 Subject: [PATCH] Fri Nov 4 19:43:01 2005 Ben Pfaff * recode.c: Rewrote whole file, as clean-up. Fri Nov 4 19:37:50 2005 Ben Pfaff * pool.c: Don't make alignment exception for x86. (pool_alloc) Return null for 0-size blocks. (pool_alloc_unaligned) New function. (pool_strndup) Removed. Changed callers to use pool_clone_unaligned(). (pool_clone_unaligned) New function. (pool_strdup) Use pool_clone_unaligned(). * var.h: (enum var_type) Give the NUMERIC, ALPHA enum this name. (struct variable) Use `enum var_type' for `type'. * vars-atr.c: (var_type_adj) New function. (var_type_noun) New function. * vars-prs: (parse_mixed_vars) Fix freeing code. (parse_mixed_vars_pool) New function. --- src/ChangeLog | 22 + src/autorecode.c | 4 +- src/lexer.h | 1 - src/pool.c | 86 +-- src/pool.h | 4 +- src/recode.c | 1384 +++++++++++++++++----------------------------- src/var.h | 14 +- src/vars-atr.c | 15 + src/vars-prs.c | 26 +- 9 files changed, 633 insertions(+), 923 deletions(-) diff --git a/src/ChangeLog b/src/ChangeLog index ba6d517a..15429058 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,25 @@ +Fri Nov 4 19:43:01 2005 Ben Pfaff + + * recode.c: Rewrote whole file, as clean-up. + +Fri Nov 4 19:37:50 2005 Ben Pfaff + + * pool.c: Don't make alignment exception for x86. + (pool_alloc) Return null for 0-size blocks. + (pool_alloc_unaligned) New function. + (pool_strndup) Removed. Changed callers to use pool_clone_unaligned(). + (pool_clone_unaligned) New function. + (pool_strdup) Use pool_clone_unaligned(). + + * var.h: (enum var_type) Give the NUMERIC, ALPHA enum this name. + (struct variable) Use `enum var_type' for `type'. + + * vars-atr.c: (var_type_adj) New function. + (var_type_noun) New function. + + * vars-prs: (parse_mixed_vars) Fix freeing code. + (parse_mixed_vars_pool) New function. + Wed Nov 2 21:24:48 2005 Ben Pfaff * file-handle-def.c: Needed another #include, to avoid missing diff --git a/src/autorecode.c b/src/autorecode.c index 5240423d..e34e395c 100644 --- a/src/autorecode.c +++ b/src/autorecode.c @@ -354,8 +354,8 @@ autorecode_proc_func (struct ccase *c, void *arc_) if (arc->src_vars[i]->type == NUMERIC) vp->f = v.f; else - vp->c = pool_strndup (arc->src_values_pool, - v.c, arc->src_vars[i]->width); + vp->c = pool_clone (arc->src_values_pool, + v.c, arc->src_vars[i]->width); *vpp = vp; } } diff --git a/src/lexer.h b/src/lexer.h index 6e8dd48f..08807733 100644 --- a/src/lexer.h +++ b/src/lexer.h @@ -63,7 +63,6 @@ int lex_force_int (void); int lex_force_num (void); int lex_force_id (void); int lex_force_string (void); - /* Weird token functions. */ int lex_look_ahead (void); diff --git a/src/pool.c b/src/pool.c index e8aede40..b8ca4ad1 100644 --- a/src/pool.c +++ b/src/pool.c @@ -97,11 +97,7 @@ union align /* This should be the alignment size used by malloc(). The size of the union above is correct, if not optimal, in all known cases. */ -#if defined (i386) || defined (__i386__) -#define ALIGN_SIZE 4 /* Save some extra memory. */ -#else #define ALIGN_SIZE sizeof (union align) -#endif /* DISCRETE_BLOCKS may be declared as nonzero to prevent suballocation of blocks. This is useful under memory @@ -245,11 +241,15 @@ pool_clear (struct pool *pool) /* Suballocation routines. */ /* Allocates a memory region AMT bytes in size from POOL and returns a - pointer to the region's start. */ + pointer to the region's start. + The region is properly aligned for storing any object. */ void * pool_alloc (struct pool *pool, size_t amt) { assert (pool != NULL); + + if (amt == 0) + return NULL; #ifndef DISCRETE_BLOCKS if (amt <= MAX_SUBALLOC) @@ -295,6 +295,40 @@ pool_alloc (struct pool *pool, size_t amt) return pool_malloc (pool, amt); } +/* Allocates a memory region AMT bytes in size from POOL and + returns a pointer to the region's start. The region is not + necessarily aligned, so it is most suitable for storing + strings. */ +void * +pool_alloc_unaligned (struct pool *pool, size_t amt) +{ + assert (pool != NULL); + +#ifndef DISCRETE_BLOCKS + /* Strings need not be aligned on any boundary, but some + operations may be more efficient when they are. However, + that's only going to help with reasonably long strings. */ + if (amt < ALIGN_SIZE) + { + if (amt == 0) + return NULL; + else + { + struct pool_block *const b = pool->blocks; + + if (b->ofs + amt <= BLOCK_SIZE) + { + void *p = ((char *) b) + b->ofs; + b->ofs += amt; + return p; + } + } + } +#endif + + return pool_alloc (pool, amt); +} + /* Allocates a memory region N * S bytes in size from POOL and returns a pointer to the region's start. N must be nonnegative, S must be positive. @@ -318,40 +352,14 @@ pool_clone (struct pool *pool, const void *buffer, size_t size) return block; } -/* Duplicates STRING, which has LENGTH characters, within POOL, - and returns a pointer to the duplicate. LENGTH should not - include the null terminator, which is always added to the - duplicate. For use only with strings, because the returned - pointere may not be aligned properly for other types. */ -char * -pool_strndup (struct pool *pool, const char *string, size_t length) +/* Allocates SIZE bytes of unaligned data in POOL, copies BUFFER + into it, and returns the new copy. */ +void * +pool_clone_unaligned (struct pool *pool, const void *buffer, size_t size) { - size_t size; - char *copy; - - assert (pool && string); - size = length + 1; - - /* Note that strings need not be aligned on any boundary. */ -#ifndef DISCRETE_BLOCKS - { - struct pool_block *const b = pool->blocks; - - if (b->ofs + size <= BLOCK_SIZE) - { - copy = ((char *) b) + b->ofs; - b->ofs += size; - } - else - copy = pool_alloc (pool, size); - } -#else - copy = pool_alloc (pool, size); -#endif - - memcpy (copy, string, length); - copy[length] = '\0'; - return copy; + void *block = pool_alloc_unaligned (pool, size); + memcpy (block, buffer, size); + return block; } /* Duplicates null-terminated STRING, within POOL, and returns a @@ -361,7 +369,7 @@ pool_strndup (struct pool *pool, const char *string, size_t length) char * pool_strdup (struct pool *pool, const char *string) { - return pool_strndup (pool, string, strlen (string)); + return pool_clone_unaligned (pool, string, strlen (string) + 1); } /* Standard allocation routines. */ diff --git a/src/pool.h b/src/pool.h index fc23df05..a5d91dd7 100644 --- a/src/pool.h +++ b/src/pool.h @@ -50,8 +50,10 @@ void *pool_create_at_offset (size_t struct_size, size_t pool_member_offset); void *pool_alloc (struct pool *, size_t) MALLOC_LIKE; void *pool_nalloc (struct pool *, size_t n, size_t s) MALLOC_LIKE; void *pool_clone (struct pool *, const void *, size_t) MALLOC_LIKE; + +void *pool_alloc_unaligned (struct pool *, size_t) MALLOC_LIKE; +void *pool_clone_unaligned (struct pool *, const void *, size_t) MALLOC_LIKE; char *pool_strdup (struct pool *, const char *) MALLOC_LIKE; -char *pool_strndup (struct pool *, const char *, size_t) MALLOC_LIKE; char *pool_strcat (struct pool *, const char *, ...) MALLOC_LIKE; /* Standard allocation routines. */ diff --git a/src/recode.c b/src/recode.c index 394e9be1..b274ed02 100644 --- a/src/recode.c +++ b/src/recode.c @@ -25,10 +25,13 @@ #include "alloc.h" #include "case.h" #include "command.h" +#include "data-in.h" #include "dictionary.h" #include "error.h" #include "lexer.h" #include "magic.h" +#include "pool.h" +#include "range-prs.h" #include "str.h" #include "var.h" @@ -38,78 +41,83 @@ /* Definitions. */ /* Type of source value for RECODE. */ -enum +enum map_in_type { - RCD_END, /* sentinel value */ - RCD_USER, /* user-missing => one */ - RCD_SINGLE, /* one => one */ - RCD_HIGH, /* x > a => one */ - RCD_LOW, /* x < b => one */ - RCD_RANGE, /* b < x < a => one */ - RCD_ELSE, /* any but SYSMIS => one */ - RCD_CONVERT /* "123" => 123 */ + MAP_SINGLE, /* Specific value. */ + MAP_RANGE, /* Range of values. */ + MAP_SYSMIS, /* System missing value. */ + MAP_MISSING, /* Any missing value. */ + MAP_ELSE, /* Any value. */ + MAP_CONVERT /* "123" => 123. */ }; -/* Describes how to recode a single value or range of values into a - single value. */ -struct coding +/* Describes input values to be mapped. */ +struct map_in { - int type; /* RCD_* */ - union value f1, f2; /* Describe value or range as src. Long - strings are stored in `c'. */ - union value t; /* Describes value as dest. Long strings in `c'. */ + enum map_in_type type; /* One of MAP_*. */ + union value x, y; /* Source values. */ }; -/* Describes how to recode a single variable. */ -struct rcd_var +/* Describes the value used as output from a mapping. */ +struct map_out { - struct rcd_var *next; - - unsigned flags; /* RCD_SRC_* | RCD_DEST_* | RCD_MISC_* */ - - struct variable *src; /* Source variable. */ - struct variable *dest; /* Destination variable. */ - char dest_name[LONG_NAME_LEN + 1]; /* Name of dest variable if we're creating it. */ - - int has_sysmis; /* Do we recode for SYSMIS? */ - union value sysmis; /* Coding for SYSMIS (if src is numeric). */ + bool copy_input; /* If true, copy input to output. */ + union value value; /* If copy_input false, recoded value. */ + int width; /* If copy_input false, output value width. */ + }; - struct coding *map; /* Coding for other values. */ - size_t nmap, mmap; /* Length of map, max capacity of map. */ +/* Describes how to recode a single value or range of values into a + single value. */ +struct mapping + { + struct map_in in; /* Input values. */ + struct map_out out; /* Output value. */ }; /* RECODE transformation. */ struct recode_trns { - struct rcd_var *codings; + struct pool *pool; + + /* Variable types, for convenience. */ + enum var_type src_type; /* src_vars[*]->type. */ + enum var_type dst_type; /* dst_vars[*]->type. */ + + /* Variables. */ + struct variable **src_vars; /* Source variables. */ + struct variable **dst_vars; /* Destination variables. */ + char **dst_names; /* Name of dest variables, if they're new. */ + size_t var_cnt; /* Number of variables. */ + + /* Mappings. */ + struct mapping *mappings; /* Value mappings. */ + size_t map_cnt; /* Number of mappings. */ }; -/* What we're recoding from (`src'==`source'). */ -#define RCD_SRC_ERROR 0000u /* Bad value for src. */ -#define RCD_SRC_NUMERIC 0001u /* Src is numeric. */ -#define RCD_SRC_STRING 0002u /* Src is short string. */ -#define RCD_SRC_MASK 0003u /* AND mask to isolate src bits. */ - -/* What we're recoding to (`dest'==`destination'). */ -#define RCD_DEST_ERROR 0000u /* Bad value for dest. */ -#define RCD_DEST_NUMERIC 0004u /* Dest is numeric. */ -#define RCD_DEST_STRING 0010u /* Dest is short string. */ -#define RCD_DEST_MASK 0014u /* AND mask to isolate dest bits. */ - -/* Miscellaneous bits. */ -#define RCD_MISC_CREATE 0020u /* We create dest var (numeric only) */ -#define RCD_MISC_DUPLICATE 0040u /* This var_info has the same MAP - value as the previous var_info. - Prevents redundant free()ing. */ -#define RCD_MISC_MISSING 0100u /* Encountered MISSING or SYSMIS in - this input spec. */ - -static int parse_dest_spec (struct rcd_var *rcd, union value *v, - size_t *max_dst_width); -static int parse_src_spec (struct rcd_var *rcd, int type, size_t max_src_width); +static bool parse_src_vars (struct recode_trns *); +static bool parse_mappings (struct recode_trns *); +static bool parse_dst_vars (struct recode_trns *); + +static void add_mapping (struct recode_trns *, + size_t *map_allocated, const struct map_in *); + +static bool parse_map_in (struct map_in *, struct pool *, + enum var_type src_type, size_t max_src_width); +static void set_map_in_generic (struct map_in *, enum map_in_type); +static void set_map_in_num (struct map_in *, enum map_in_type, double, double); +static void set_map_in_str (struct map_in *, struct pool *, + const struct string *, size_t width); + +static bool parse_map_out (struct pool *, struct map_out *); +static void set_map_out_num (struct map_out *, double); +static void set_map_out_str (struct map_out *, struct pool *, + const struct string *); + +static void enlarge_dst_widths (struct recode_trns *); +static void create_dst_vars (struct recode_trns *); + static trns_proc_func recode_trns_proc; static trns_free_func recode_trns_free; -static double convert_to_double (const char *, int); /* Parser. */ @@ -117,906 +125,536 @@ static double convert_to_double (const char *, int); int cmd_recode (void) { - size_t i; - - /* Transformation that we're constructing. */ - struct rcd_var *rcd; + do + { + struct recode_trns *trns + = pool_create_container (struct recode_trns, pool); + + /* Parse source variable names, + then input to output mappings, + then destintation variable names. */ + if (!parse_src_vars (trns) + || !parse_mappings (trns) + || !parse_dst_vars (trns)) + { + recode_trns_free (trns); + return CMD_PART_SUCCESS; + } + + /* Ensure that all the output strings are at least as wide + as the widest destination variable. */ + if (trns->dst_type == ALPHA) + enlarge_dst_widths (trns); + + /* Create destination variables, if needed. + This must be the final step; otherwise we'd have to + delete destination variables on failure. */ + if (trns->src_vars != trns->dst_vars) + create_dst_vars (trns); + + /* Done. */ + add_transformation (recode_trns_proc, recode_trns_free, trns); + } + while (lex_match ('/')); + + return lex_end_of_command (); +} - /* Type of the src variables. */ - int type; +/* Parses a set of variables to recode into TRNS->src_vars and + TRNS->var_cnt. Sets TRNS->src_type. Returns true if + successful, false on parse error. */ +static bool +parse_src_vars (struct recode_trns *trns) +{ + if (!parse_variables (default_dict, &trns->src_vars, &trns->var_cnt, + PV_SAME_TYPE)) + return false; + pool_register (trns->pool, free, trns->src_vars); + trns->src_type = trns->src_vars[0]->type; + return true; +} - /* Length of longest src string. */ +/* Parses a set of mappings, which take the form (input=output), + into TRNS->mappings and TRNS->map_cnt. Sets TRNS->dst_type. + Returns true if successful, false on parse error. */ +static bool +parse_mappings (struct recode_trns *trns) +{ size_t max_src_width; + size_t map_allocated; + bool have_dst_type; + size_t i; + + /* Find length of longest source variable. */ + max_src_width = trns->src_vars[0]->width; + for (i = 1; i < trns->var_cnt; i++) + { + size_t var_width = trns->src_vars[i]->width; + if (var_width > max_src_width) + max_src_width = var_width; + } + + /* Parse the mappings in parentheses. */ + trns->mappings = NULL; + trns->map_cnt = 0; + map_allocated = 0; + have_dst_type = false; + if (!lex_force_match ('(')) + return false; + do + { + enum var_type dst_type; - /* Length of longest dest string. */ - size_t max_dst_width; + if (!lex_match_id ("CONVERT")) + { + struct map_out out; + size_t first_map_idx; + size_t i; - /* For stepping through, constructing the linked list of - recodings. */ - struct rcd_var *iter; + first_map_idx = trns->map_cnt; - /* The real transformation, just a wrapper for a list of - rcd_var's. */ - struct recode_trns *trns; + /* Parse source specifications. */ + do + { + struct map_in in; + if (!parse_map_in (&in, trns->pool, + trns->src_type, max_src_width)) + return false; + add_mapping (trns, &map_allocated, &in); + lex_match (','); + } + while (!lex_match ('=')); + + if (!parse_map_out (trns->pool, &out)) + return false; + dst_type = out.width == 0 ? NUMERIC : ALPHA; + if (have_dst_type && dst_type != trns->dst_type) + { + msg (SE, _("Inconsistent target variable types. " + "Target variables " + "must be all numeric or all string.")); + return false; + } + + for (i = first_map_idx; i < trns->map_cnt; i++) + trns->mappings[i].out = out; + } + else + { + /* Parse CONVERT as a special case. */ + struct map_in in; + set_map_in_generic (&in, MAP_CONVERT); + add_mapping (trns, &map_allocated, &in); + + dst_type = NUMERIC; + if (trns->src_type != ALPHA + || (have_dst_type && trns->dst_type != NUMERIC)) + { + msg (SE, _("CONVERT requires string input values and " + "numeric output values.")); + return false; + } + } + trns->dst_type = dst_type; + have_dst_type = true; - /* First transformation in the list. rcd is in this list. */ - struct rcd_var *head; + if (!lex_force_match (')')) + return false; + } + while (lex_match ('(')); - /* Variables in the current part of the recoding. */ - struct variable **v; - size_t nv; + return true; +} - /* Parses each specification between slashes. */ - head = rcd = xmalloc (sizeof *rcd); - v = NULL; - for (;;) +/* Parses a mapping input value into IN, allocating memory from + POOL. The source value type must be provided as SRC_TYPE and, + if string, the maximum width of a string source variable must + be provided in MAX_SRC_WIDTH. Returns true if successful, + false on parse error. */ +static bool +parse_map_in (struct map_in *in, struct pool *pool, + enum var_type src_type, size_t max_src_width) +{ + if (lex_match_id ("ELSE")) + set_map_in_generic (in, MAP_ELSE); + else if (src_type == NUMERIC) { - /* Whether we've already encountered a specification for SYSMIS. */ - int had_sysmis = 0; - - /* Initialize this rcd_var to ensure proper cleanup. */ - rcd->next = NULL; - rcd->map = NULL; - rcd->nmap = rcd->mmap = 0; - rcd->has_sysmis = 0; - rcd->sysmis.f = 0; - - /* Parse variable names. */ - if (!parse_variables (default_dict, &v, &nv, PV_SAME_TYPE)) - goto lossage; - - /* Ensure all variables are same type; find length of longest - source variable. */ - type = v[0]->type; - max_src_width = v[0]->width; - - if (type == ALPHA) - for (i = 0; i < nv; i++) - if (v[i]->width > (int) max_src_width) - max_src_width = v[i]->width; - - /* Set up flags. */ - rcd->flags = 0; - if (type == NUMERIC) - rcd->flags |= RCD_SRC_NUMERIC; - else - rcd->flags |= RCD_SRC_STRING; - - /* Parse each coding in parentheses. */ - max_dst_width = 0; - if (!lex_force_match ('(')) - goto lossage; - for (;;) - { - /* Get the input value (before the `='). */ - size_t mark = rcd->nmap; - int code = parse_src_spec (rcd, type, max_src_width); - if (!code) - goto lossage; - - /* ELSE is the same as any other input spec except that it - precludes later sysmis specifications. */ - if (code == 3) - { - had_sysmis = 1; - code = 1; - } - - /* If keyword CONVERT was specified, there is no output - specification. */ - if (code == 1) - { - union value output; - - /* Get the output value (after the `='). */ - lex_get (); /* Skip `='. */ - if (!parse_dest_spec (rcd, &output, &max_dst_width)) - goto lossage; - - /* Set the value for SYSMIS if requested and if we don't - already have one. */ - if ((rcd->flags & RCD_MISC_MISSING) && !had_sysmis) - { - rcd->has_sysmis = 1; - if ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC) - rcd->sysmis.f = output.f; - else - rcd->sysmis.c = xstrdup (output.c); - had_sysmis = 1; - - rcd->flags &= ~RCD_MISC_MISSING; - } - - /* Since there may be multiple input values for a single - output, the output value need to propagated among all - of them. */ - if ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC) - for (i = mark; i < rcd->nmap; i++) - rcd->map[i].t.f = output.f; - else - { - for (i = mark; i < rcd->nmap; i++) - rcd->map[i].t.c = output.c ? xstrdup (output.c) : NULL; - free (output.c); - } - } - lex_get (); /* Skip `)'. */ - if (!lex_match ('(')) - break; - } - - /* Append sentinel value. */ - rcd->map[rcd->nmap++].type = RCD_END; - - /* Since multiple variables may use the same recodings, it is - necessary to propogate the codings to all of them. */ - rcd->src = v[0]; - rcd->dest = v[0]; - rcd->dest_name[0] = 0; - iter = rcd; - for (i = 1; i < nv; i++) - { - iter = iter->next = xmalloc (sizeof *iter); - iter->next = NULL; - iter->flags = rcd->flags | RCD_MISC_DUPLICATE; - iter->src = v[i]; - iter->dest = v[i]; - iter->dest_name[0] = 0; - iter->has_sysmis = rcd->has_sysmis; - iter->sysmis = rcd->sysmis; - iter->map = rcd->map; - } - - if (lex_match_id ("INTO")) - { - char **names; - size_t nnames; - - int success = 0; - - if (!parse_mixed_vars (&names, &nnames, PV_NONE)) - goto lossage; - - if (nnames != nv) - { - for (i = 0; i < nnames; i++) - free (names[i]); - free (names); - msg (SE, _("%u variable(s) cannot be recoded into " - "%u variable(s). Specify the same number " - "of variables as input and output variables."), - (unsigned) nv, (unsigned) nnames); - goto lossage; - } - - if ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_STRING) - for (i = 0, iter = rcd; i < nv; i++, iter = iter->next) - { - struct variable *v = dict_lookup_var (default_dict, names[i]); - - if (!v) - { - msg (SE, _("There is no string variable named " - "%s. (All string variables specified " - "on INTO must already exist. Use the " - "STRING command to create a string " - "variable.)"), - names[i]); - goto INTO_fail; - } - if (v->type != ALPHA) - { - msg (SE, _("Type mismatch between input and output " - "variables. Output variable %s is not " - "a string variable, but all the input " - "variables are string variables."), - v->name); - goto INTO_fail; - } - if (v->width > (int) max_dst_width) - max_dst_width = v->width; - iter->dest = v; - } - else - for (i = 0, iter = rcd; i < nv; i++, iter = iter->next) - { - struct variable *v = dict_lookup_var (default_dict, names[i]); - - if (v) - { - if (v->type != NUMERIC) - { - msg (SE, _("Type mismatch after INTO: %s " - "is not a numeric variable."), v->name); - goto INTO_fail; - } - else - iter->dest = v; - } - else - strcpy (iter->dest_name, names[i]); - } - success = 1; - - /* Note that regardless of whether we succeed or fail, - flow-of-control comes here. `success' is the important - factor. Ah, if C had garbage collection... */ - INTO_fail: - for (i = 0; i < nnames; i++) - free (names[i]); - free (names); - if (!success) - goto lossage; - } - else - { - if (max_src_width > max_dst_width) - max_dst_width = max_src_width; - - if ((rcd->flags & RCD_SRC_MASK) == RCD_SRC_NUMERIC - && (rcd->flags & RCD_DEST_MASK) != RCD_DEST_NUMERIC) - { - msg (SE, _("INTO must be used when the input values are " - "numeric and output values are string.")); - goto lossage; - } - - if ((rcd->flags & RCD_SRC_MASK) != RCD_SRC_NUMERIC - && (rcd->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC) - { - msg (SE, _("INTO must be used when the input values are " - "string and output values are numeric.")); - goto lossage; - } - } - - if ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_STRING) - { - struct coding *cp; - - for (cp = rcd->map; cp->type != RCD_END; cp++) - if (cp->t.c) - { - if (strlen (cp->t.c) < max_dst_width) - { - /* The NULL is only really necessary for the - debugging code. */ - char *repl = xmalloc (max_dst_width + 1); - str_copy_rpad (repl, max_dst_width + 1, cp->t.c); - free (cp->t.c); - cp->t.c = repl; - } - else - /* The strings are guaranteed to be in order of - nondecreasing length. */ - break; - } - - } - - free (v); - v = NULL; - - if (!lex_match ('/')) - break; - while (rcd->next) - rcd = rcd->next; - rcd = rcd->next = xmalloc (sizeof *rcd); + if (lex_match_id ("MISSING")) + set_map_in_generic (in, MAP_MISSING); + else if (lex_match_id ("SYSMIS")) + set_map_in_generic (in, MAP_SYSMIS); + else + { + double x, y; + if (!parse_num_range (&x, &y, NULL)) + return false; + set_map_in_num (in, x == y ? MAP_SINGLE : MAP_RANGE, x, y); + } } - - if (token != '.') + else { - lex_error (_("expecting end of command")); - goto lossage; + if (!lex_force_string ()) + return false; + set_map_in_str (in, pool, &tokstr, max_src_width); + lex_get (); } - for (rcd = head; rcd; rcd = rcd->next) - if (rcd->dest_name[0]) - { - rcd->dest = dict_create_var (default_dict, rcd->dest_name, 0); - if (!rcd->dest) - { - /* FIXME: This can fail if a destname is duplicated. - We could give an error at parse time but I don't - care enough. */ - rcd->dest = dict_lookup_var_assert (default_dict, rcd->dest_name); - } - } - - trns = xmalloc (sizeof *trns); - trns->codings = head; - add_transformation (recode_trns_proc, recode_trns_free, trns); - - return CMD_SUCCESS; - - lossage: - free (v); - { - struct recode_trns t; + return true; +} - t.codings = head; - recode_trns_free (&t); - return CMD_FAILURE; - } +/* Adds IN to the list of mappings in TRNS. + MAP_ALLOCATED is the current number of allocated mappings, + which is updated as needed. */ +static void +add_mapping (struct recode_trns *trns, + size_t *map_allocated, const struct map_in *in) +{ + struct mapping *m; + if (trns->map_cnt >= *map_allocated) + trns->mappings = pool_2nrealloc (trns->pool, trns->mappings, + map_allocated, + sizeof *trns->mappings); + m = &trns->mappings[trns->map_cnt++]; + m->in = *in; } -static int -parse_dest_spec (struct rcd_var *rcd, union value *v, size_t *max_dst_width) +/* Sets IN as a mapping of the given TYPE. */ +static void +set_map_in_generic (struct map_in *in, enum map_in_type type) +{ + in->type = type; +} + +/* Sets IN as a numeric mapping of the given TYPE, + with X and Y as the two numeric values. */ +static void +set_map_in_num (struct map_in *in, enum map_in_type type, double x, double y) { - int flags; + in->type = type; + in->x.f = x; + in->y.f = y; +} - v->c = NULL; +/* Sets IN as a string mapping, with STRING as the string, + allocated from POOL. The string is padded with spaces on the + right to WIDTH characters long. */ +static void +set_map_in_str (struct map_in *in, struct pool *pool, + const struct string *string, size_t width) +{ + in->type = MAP_SINGLE; + in->x.c = pool_alloc_unaligned (pool, width); + buf_copy_rpad (in->x.c, width, ds_data (string), ds_length (string)); +} +/* Parses a mapping output value into OUT, allocating memory from + POOL. Returns true if successful, false on parse error. */ +static bool +parse_map_out (struct pool *pool, struct map_out *out) +{ if (lex_is_number ()) { - v->f = tokval; + set_map_out_num (out, lex_number ()); lex_get (); - flags = RCD_DEST_NUMERIC; } else if (lex_match_id ("SYSMIS")) - { - v->f = SYSMIS; - flags = RCD_DEST_NUMERIC; - } + set_map_out_num (out, SYSMIS); else if (token == T_STRING) { - size_t max = *max_dst_width; - size_t toklen = ds_length (&tokstr); - if (toklen > max) - max = toklen; - v->c = xmalloc (max + 1); - str_copy_rpad (v->c, max + 1, ds_c_str (&tokstr)); - flags = RCD_DEST_STRING; - *max_dst_width = max; + set_map_out_str (out, pool, &tokstr); lex_get (); } else if (lex_match_id ("COPY")) - { - if ((rcd->flags & RCD_SRC_MASK) == RCD_SRC_NUMERIC) - { - flags = RCD_DEST_NUMERIC; - v->f = -SYSMIS; - } - else - { - flags = RCD_DEST_STRING; - v->c = NULL; - } - } + out->copy_input = true; else { lex_error (_("expecting output value")); - return 0; + return false; } + return true; +} - if ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_ERROR) - rcd->flags |= flags; -#if 0 - else if (((rcd->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC - && flags != RCD_DEST_NUMERIC) - || ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_STRING - && flags != RCD_DEST_STRING)) -#endif - else if ((rcd->flags & RCD_DEST_MASK) ^ flags) - { - msg (SE, _("Inconsistent output types. The output values " - "must be all numeric or all string.")); - return 0; - } - - return 1; +/* Sets OUT as a numeric mapping output with the given VALUE. */ +static void +set_map_out_num (struct map_out *out, double value) +{ + out->copy_input = false; + out->value.f = value; + out->width = 0; } -/* Reads a set of source specifications and returns one of the - following values: 0 on failure; 1 for normal success; 2 for success - but with CONVERT as the keyword; 3 for success but with ELSE as the - keyword. */ -static int -parse_src_spec (struct rcd_var *rcd, int type, size_t max_src_width) +/* Sets OUT as a string mapping output with the given VALUE. */ +static void +set_map_out_str (struct map_out *out, struct pool *pool, + const struct string *value) { - struct coding *c; + const char *string = ds_data (value); + size_t length = ds_length (value); - for (;;) - { - if (rcd->nmap + 1 >= rcd->mmap) - { - rcd->mmap += 16; - rcd->map = xnrealloc (rcd->map, rcd->mmap, sizeof *rcd->map); - } - - c = &rcd->map[rcd->nmap]; - c->f1.c = c->f2.c = NULL; - if (lex_match_id ("ELSE")) - { - c->type = RCD_ELSE; - rcd->nmap++; - return 3; - } - else if (type == NUMERIC) - { - if (token == T_ID) - { - if (lex_match_id ("LO") || lex_match_id ("LOWEST")) - { - if (!lex_force_match_id ("THRU")) - return 0; - if (lex_match_id ("HI") || lex_match_id ("HIGHEST")) - c->type = RCD_ELSE; - else if (lex_is_number ()) - { - c->type = RCD_LOW; - c->f1.f = tokval; - lex_get (); - } - else - { - lex_error (_("following LO THRU")); - return 0; - } - } - else if (lex_match_id ("MISSING")) - { - c->type = RCD_USER; - rcd->flags |= RCD_MISC_MISSING; - } - else if (lex_match_id ("SYSMIS")) - { - c->type = RCD_END; - rcd->flags |= RCD_MISC_MISSING; - } - else - { - lex_error (_("in source value")); - return 0; - } - } - else if (lex_is_number ()) - { - c->f1.f = tokval; - lex_get (); - if (lex_match_id ("THRU")) - { - if (lex_match_id ("HI") || lex_match_id ("HIGHEST")) - c->type = RCD_HIGH; - else if (lex_is_number ()) - { - c->type = RCD_RANGE; - c->f2.f = tokval; - lex_get (); - } - else - { - lex_error (NULL); - return 0; - } - } - else - c->type = RCD_SINGLE; - } - else - { - lex_error (_("in source value")); - return 0; - } - } - else - { - assert (type == ALPHA); - if (lex_match_id ("CONVERT")) - { - if ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_ERROR) - rcd->flags |= RCD_DEST_NUMERIC; - else if ((rcd->flags & RCD_DEST_MASK) != RCD_DEST_NUMERIC) - { - msg (SE, _("Keyword CONVERT may only be used with " - "string input values and numeric output " - "values.")); - return 0; - } - - c->type = RCD_CONVERT; - rcd->nmap++; - return 2; - } - else - { - /* Only the debugging code needs the NULLs at the ends - of the strings. However, changing code behavior more - than necessary based on the DEBUGGING `#define' is just - *inviting* bugs. */ - c->type = RCD_SINGLE; - if (!lex_force_string ()) - return 0; - c->f1.c = xmalloc (max_src_width + 1); - str_copy_rpad (c->f1.c, max_src_width + 1, ds_c_str (&tokstr)); - lex_get (); - } - } - - if (c->type != RCD_END) - rcd->nmap++; - - lex_match (','); - if (token == '=') - break; - } - return 1; + out->copy_input = false; + out->value.c = pool_alloc_unaligned (pool, length); + memcpy (out->value.c, string, length); + out->width = length; } - -/* Data transformation. */ -static void -recode_trns_free (void *t_) +/* Parses a set of target variables into TRNS->dst_vars and + TRNS->dst_names. */ +static bool +parse_dst_vars (struct recode_trns *trns) { - struct recode_trns *t = t_; size_t i; - struct rcd_var *head, *next; + + if (lex_match_id ("INTO")) + { + size_t name_cnt; + size_t i; + + if (!parse_mixed_vars_pool (trns->pool, &trns->dst_names, &name_cnt, + PV_NONE)) + return false; + + if (name_cnt != trns->var_cnt) + { + msg (SE, _("%u variable(s) cannot be recoded into " + "%u variable(s). Specify the same number " + "of variables as source and target variables."), + (unsigned) trns->var_cnt, (unsigned) name_cnt); + return false; + } + + trns->dst_vars = pool_nalloc (trns->pool, + trns->var_cnt, sizeof *trns->dst_vars); + for (i = 0; i < trns->var_cnt; i++) + { + struct variable *v; + v = trns->dst_vars[i] = dict_lookup_var (default_dict, + trns->dst_names[i]); + if (v == NULL && trns->dst_type == ALPHA) + { + msg (SE, _("There is no variable named " + "%s. (All string variables specified " + "on INTO must already exist. Use the " + "STRING command to create a string " + "variable.)"), + trns->dst_names[i]); + return false; + } + } + } + else + { + trns->dst_vars = trns->src_vars; + if (trns->src_type != trns->dst_type) + { + msg (SE, _("INTO is required with %s input values " + "and %s output values."), + var_type_adj (trns->src_type), + var_type_adj (trns->dst_type)); + return false; + } + } - head = t->codings; - while (head) + for (i = 0; i < trns->var_cnt; i++) { - if (head->map && !(head->flags & RCD_MISC_DUPLICATE)) - { - if (head->flags & RCD_SRC_STRING) - for (i = 0; i < head->nmap; i++) - switch (head->map[i].type) - { - case RCD_RANGE: - free (head->map[i].f2.c); - /* fall through */ - case RCD_USER: - case RCD_SINGLE: - case RCD_HIGH: - case RCD_LOW: - free (head->map[i].f1.c); - break; - case RCD_END: - case RCD_ELSE: - case RCD_CONVERT: - break; - default: - assert (0); - } - if (head->flags & RCD_DEST_STRING) - for (i = 0; i < head->nmap; i++) - if (head->map[i].type != RCD_CONVERT && head->map[i].type != RCD_END) - free (head->map[i].t.c); - free (head->map); - } - next = head->next; - free (head); - head = next; + struct variable *v = trns->dst_vars[i]; + if (v != NULL && v->type != trns->dst_type) + { + msg (SE, _("Type mismatch. Cannot store %s data in " + "%s variable %s."), + trns->dst_type == ALPHA ? _("string") : _("numeric"), + v->type == ALPHA ? _("string") : _("numeric"), + v->name); + return false; + } } - free (t); + + return true; } -static inline struct coding * -find_src_numeric (struct rcd_var *v, struct ccase *c) +/* Ensures that all the output values in TRNS are as wide as the + widest destination variable. */ +static void +enlarge_dst_widths (struct recode_trns *trns) { - double cmp = case_num (c, v->src->fv); - struct coding *cp; + size_t max_dst_width; + size_t i; - if (cmp == SYSMIS) + max_dst_width = 0; + for (i = 0; i < trns->var_cnt; i++) { - if (v->sysmis.f != -SYSMIS) - { - if ((v->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC) - case_data_rw (c, v->dest->fv)->f = v->sysmis.f; - else - memcpy (case_data_rw (c, v->dest->fv)->s, v->sysmis.s, - v->dest->width); - } - return NULL; + struct variable *v = trns->dst_vars[i]; + if (v->width > max_dst_width) + max_dst_width = v->width; } - for (cp = v->map;; cp++) - switch (cp->type) - { - case RCD_END: - return NULL; - case RCD_USER: - if (mv_is_num_user_missing (&v->src->miss, cmp)) - return cp; - break; - case RCD_SINGLE: - if (cmp == cp->f1.f) - return cp; - break; - case RCD_HIGH: - if (cmp >= cp->f1.f) - return cp; - break; - case RCD_LOW: - if (cmp <= cp->f1.f) - return cp; - break; - case RCD_RANGE: - if (cmp >= cp->f1.f && cmp <= cp->f2.f) - return cp; - break; - case RCD_ELSE: - return cp; - default: - assert (0); - } + for (i = 0; i < trns->map_cnt; i++) + { + struct map_out *out = &trns->mappings[i].out; + if (!out->copy_input && out->width < max_dst_width) + { + char *s = pool_alloc_unaligned (trns->pool, max_dst_width + 1); + str_copy_rpad (s, max_dst_width + 1, out->value.c); + out->value.c = s; + } + } } -static inline struct coding * -find_src_string (struct rcd_var *v, struct ccase *c) +/* Creates destination variables that don't already exist. */ +static void +create_dst_vars (struct recode_trns *trns) { - const char *cmp = case_str (c, v->src->fv); - int w = v->src->width; - struct coding *cp; - - for (cp = v->map;; cp++) - switch (cp->type) - { - case RCD_END: - return NULL; - case RCD_SINGLE: - if (!memcmp (cp->f1.c, cmp, w)) - return cp; - break; - case RCD_ELSE: - return cp; - case RCD_CONVERT: - { - double f = convert_to_double (cmp, w); - if (f != -SYSMIS) - { - case_data_rw (c, v->dest->fv)->f = f; - return NULL; - } - break; - } - default: - assert (0); - } + size_t i; + + for (i = 0; i < trns->var_cnt; i++) + { + struct variable **var = &trns->dst_vars[i]; + const char *name = trns->dst_names[i]; + + *var = dict_lookup_var (default_dict, name); + if (*var == NULL) + *var = dict_create_var_assert (default_dict, name, 0); + assert ((*var)->type == trns->dst_type); + } } + +/* Data transformation. */ -static int -recode_trns_proc (void *t_, struct ccase *c, - int case_idx UNUSED) +/* Returns the output mapping in TRNS for an input of VALUE on + variable V, or a null pointer if there is no mapping. */ +static const struct map_out * +find_src_numeric (struct recode_trns *trns, double value, struct variable *v) { - struct recode_trns *t = t_; - struct rcd_var *v; + struct mapping *m; - for (v = t->codings; v; v = v->next) + for (m = trns->mappings; m < trns->mappings + trns->map_cnt; m++) { - struct coding *cp; - - switch (v->flags & RCD_SRC_MASK) - { - case RCD_SRC_NUMERIC: - cp = find_src_numeric (v, c); - break; - case RCD_SRC_STRING: - cp = find_src_string (v, c); - break; + const struct map_in *in = &m->in; + const struct map_out *out = &m->out; + bool match; + + switch (in->type) + { + case MAP_SINGLE: + match = value == in->x.f; + break; + case MAP_MISSING: + match = mv_is_num_user_missing (&v->miss, value); + break; + case MAP_RANGE: + match = value >= in->x.f && value <= in->y.f; + break; + case MAP_ELSE: + match = true; + break; default: - assert (0); abort (); - } - if (!cp) - continue; - - /* A matching input value was found. */ - if ((v->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC) - { - double val = cp->t.f; - double *out = &case_data_rw (c, v->dest->fv)->f; - if (val == -SYSMIS) - *out = case_num (c, v->src->fv); - else - *out = val; - } - else - { - char *val = cp->t.c; - if (val == NULL) - { - if (v->dest->fv != v->src->fv) - buf_copy_rpad (case_data_rw (c, v->dest->fv)->s, - v->dest->width, - case_str (c, v->src->fv), v->src->width); - } - else - memcpy (case_data_rw (c, v->dest->fv)->s, cp->t.c, v->dest->width); - } + } + + if (match) + return out; } - return -1; + return NULL; } -/* Convert NPTR to a `long int' in base 10. Returns the long int on - success, NOT_LONG on failure. On success stores a pointer to the - first character after the number into *ENDPTR. From the GNU C - library. */ -static long int -string_to_long (const char *nptr, int width, const char **endptr) +/* Returns the output mapping in TRNS for an input of VALUE with + the given WIDTH, or a null pointer if there is no mapping. */ +static const struct map_out * +find_src_string (struct recode_trns *trns, const char *value, int width) { - int negative; - unsigned long int cutoff; - unsigned int cutlim; - unsigned long int i; - const char *s; - unsigned char c; - const char *save; - - s = nptr; - - /* Check for a sign. */ - if (*s == '-') - { - negative = 1; - ++s; - } - else if (*s == '+') - { - negative = 0; - ++s; - } - else - negative = 0; - if (s >= nptr + width) - return NOT_LONG; - - /* Save the pointer so we can check later if anything happened. */ - save = s; + struct mapping *m; - cutoff = ULONG_MAX / 10ul; - cutlim = ULONG_MAX % 10ul; - - i = 0; - for (c = *s;;) + for (m = trns->mappings; m < trns->mappings + trns->map_cnt; m++) { - if (isdigit ((unsigned char) c)) - c -= '0'; - else - break; - /* Check for overflow. */ - if (i > cutoff || (i == cutoff && c > cutlim)) - return NOT_LONG; - else - i = i * 10ul + c; + const struct map_in *in = &m->in; + struct map_out *out = &m->out; + bool match; + + switch (in->type) + { + case MAP_SINGLE: + match = !memcmp (value, in->x.c, width); + break; + case MAP_ELSE: + match = true; + break; + case MAP_CONVERT: + { + struct data_in di; + + di.s = value; + di.e = value + width; + di.v = &out->value; + di.flags = DI_IGNORE_ERROR; + di.f1 = di.f2 = 0; + di.format.type = FMT_F; + di.format.w = width; + di.format.d = 0; + match = data_in (&di); + break; + } + default: + abort (); + } - s++; - if (s >= nptr + width) - break; - c = *s; + if (match) + return out; } - /* Check if anything actually happened. */ - if (s == save) - return NOT_LONG; - - /* Check for a value that is within the range of `unsigned long - int', but outside the range of `long int'. We limit LONG_MIN and - LONG_MAX by one point because we know that NOT_LONG is out there - somewhere. */ - if (i > (negative - ? -((unsigned long int) LONG_MIN) - 1 - : ((unsigned long int) LONG_MAX) - 1)) - return NOT_LONG; - - *endptr = s; - - /* Return the result of the appropriate sign. */ - return (negative ? -i : i); + return NULL; } -/* Converts S to a double according to format Fx.0. Returns the value - found, or -SYSMIS if there was no valid number in s. WIDTH is the - length of string S. From the GNU C library. */ -static double -convert_to_double (const char *s, int width) +/* Performs RECODE transformation. */ +static int +recode_trns_proc (void *trns_, struct ccase *c, int case_idx UNUSED) { - const char *end = &s[width]; - - short int sign; - - /* The number so far. */ - double num; - - int got_dot; /* Found a decimal point. */ - int got_digit; /* Count of digits. */ - - /* The exponent of the number. */ - long int exponent; - - /* Eat whitespace. */ - while (s < end && isspace ((unsigned char) *s)) - ++s; - if (s >= end) - return SYSMIS; - - /* Get the sign. */ - sign = *s == '-' ? -1 : 1; - if (*s == '-' || *s == '+') - { - ++s; - if (s >= end) - return -SYSMIS; - } + struct recode_trns *trns = trns_; + size_t i; - num = 0.0; - got_dot = 0; - got_digit = 0; - exponent = 0; - for (; s < end; ++s) + for (i = 0; i < trns->var_cnt; i++) { - if (isdigit ((unsigned char) *s)) - { - got_digit++; - - /* Make sure that multiplication by 10 will not overflow. */ - if (num > DBL_MAX * 0.1) - /* The value of the digit doesn't matter, since we have already - gotten as many digits as can be represented in a `double'. - This doesn't necessarily mean the result will overflow. - The exponent may reduce it to within range. - - We just need to record that there was another - digit so that we can multiply by 10 later. */ - ++exponent; - else - num = (num * 10.0) + (*s - '0'); - - /* Keep track of the number of digits after the decimal point. - If we just divided by 10 here, we would lose precision. */ - if (got_dot) - --exponent; - } - else if (!got_dot && *s == '.') - /* Record that we have found the decimal point. */ - got_dot = 1; - else - break; - } + struct variable *src_var = trns->src_vars[i]; + struct variable *dst_var = trns->dst_vars[i]; - if (!got_digit) - return -SYSMIS; + const union value *src_data = case_data (c, src_var->fv); + union value *dst_data = case_data_rw (c, dst_var->fv); - if (s < end && (tolower ((unsigned char) (*s)) == 'e' - || tolower ((unsigned char) (*s)) == 'd')) - { - /* Get the exponent specified after the `e' or `E'. */ - long int exp; + const struct map_out *out; - s++; - if (s >= end) - return -SYSMIS; - - exp = string_to_long (s, end - s, &s); - if (exp == NOT_LONG || end == s) - return -SYSMIS; - exponent += exp; + if (trns->src_type == NUMERIC) + out = find_src_numeric (trns, src_data->f, src_var); + else + out = find_src_string (trns, src_data->s, src_var->width); + + if (trns->dst_type == NUMERIC) + { + if (out != NULL) + dst_data->f = !out->copy_input ? out->value.f : src_data->f; + else if (trns->src_vars != trns->dst_vars) + dst_data->f = SYSMIS; + } + else + { + if (out != NULL) + { + if (!out->copy_input) + memcpy (dst_data->s, out->value.c, dst_var->width); + else if (trns->src_vars != trns->dst_vars) + buf_copy_rpad (dst_data->s, dst_var->width, + src_data->s, src_var->width); + } + else if (trns->src_vars != trns->dst_vars) + memset (dst_data->s, ' ', dst_var->width); + } } - while (s < end && isspace ((unsigned char) *s)) - s++; - if (s < end) - return -SYSMIS; - - if (num == 0.0) - return 0.0; - - /* Multiply NUM by 10 to the EXPONENT power, - checking for overflow and underflow. */ - - if (exponent < 0) - { - if (-exponent + got_digit > -(DBL_MIN_10_EXP) + 5 - || num < DBL_MIN * pow (10.0, (double) -exponent)) - return -SYSMIS; - num *= pow (10.0, (double) exponent); - } - else if (exponent > 0) - { - if (num > DBL_MAX * pow (10.0, (double) -exponent)) - return -SYSMIS; - num *= pow (10.0, (double) exponent); - } + return -1; +} - return sign > 0 ? num : -num; +/* Frees a RECODE transformation. */ +static void +recode_trns_free (void *trns_) +{ + struct recode_trns *trns = trns_; + pool_destroy (trns->pool); } diff --git a/src/var.h b/src/var.h index 802004bc..ca644a4d 100644 --- a/src/var.h +++ b/src/var.h @@ -31,19 +31,21 @@ /* Script variables. */ /* Variable type. */ -enum +enum var_type { NUMERIC, /* A numeric variable. */ - ALPHA /* A string variable. - (STRING is pre-empted by lexer.h.) */ + ALPHA /* A string variable. */ }; +const char *var_type_adj (enum var_type); +const char *var_type_noun (enum var_type); + /* A variable's dictionary entry. */ struct variable { /* Basic information. */ char name[LONG_NAME_LEN + 1]; /* Variable name. Mixed case. */ - int type; /* NUMERIC or ALPHA. */ + enum var_type type; /* NUMERIC or ALPHA. */ int width; /* Size of string variables in chars. */ int fv, nv; /* Index into `value's, number of values. */ unsigned init : 1; /* 1=VFM must init and possibly reinit. */ @@ -215,6 +217,7 @@ enum PV_NO_SCRATCH = 00200 /* Disallow scratch variables. */ }; +struct pool; struct variable *parse_variable (void); struct variable *parse_dict_variable (const struct dictionary *); int parse_variables (const struct dictionary *, struct variable ***, size_t *, @@ -223,7 +226,8 @@ int parse_var_set_vars (const struct var_set *, struct variable ***, size_t *, int opts); int parse_DATA_LIST_vars (char ***names, size_t *cnt, int opts); int parse_mixed_vars (char ***names, size_t *cnt, int opts); - +int parse_mixed_vars_pool (struct pool *, + char ***names, size_t *cnt, int opts); /* Return a string representing this variable, in the form most diff --git a/src/vars-atr.c b/src/vars-atr.c index a31bdb3c..160894b4 100644 --- a/src/vars-atr.c +++ b/src/vars-atr.c @@ -32,6 +32,21 @@ #include "gettext.h" #define _(msgid) gettext (msgid) +/* Returns an adjective describing the given variable TYPE, + suitable for use in phrases like "numeric variable". */ +const char * +var_type_adj (enum var_type type) +{ + return type == NUMERIC ? _("numeric") : _("string"); +} + +/* Returns a noun describing a value of the given variable TYPE, + suitable for use in phrases like "a number". */ +const char * +var_type_noun (enum var_type type) +{ + return type == NUMERIC ? _("number") : _("string"); +} /* Assign auxiliary data AUX to variable V, which must not already have auxiliary data. Before V's auxiliary data is diff --git a/src/vars-prs.c b/src/vars-prs.c index 920f8c91..563ac2b2 100644 --- a/src/vars-prs.c +++ b/src/vars-prs.c @@ -29,6 +29,7 @@ #include "hash.h" #include "lexer.h" #include "misc.h" +#include "pool.h" #include "size_max.h" #include "str.h" @@ -508,12 +509,33 @@ parse_mixed_vars (char ***names, size_t *nnames, int pv_opts) fail: for (i = 0; i < *nnames; i++) - free ((*names)[*nnames]); - free (names); + free ((*names)[i]); + free (*names); *names = NULL; *nnames = 0; return 0; } + +/* Parses a list of variables where some of the variables may be + existing and the rest are to be created. Same args as + parse_DATA_LIST_vars(), except that all allocations are taken + from the given POOL. */ +int +parse_mixed_vars_pool (struct pool *pool, + char ***names, size_t *nnames, int pv_opts) +{ + int retval = parse_mixed_vars (names, nnames, pv_opts); + if (retval) + { + size_t i; + + for (i = 0; i < *nnames; i++) + pool_register (pool, free, (*names)[i]); + pool_register (pool, free, *names); + } + return retval; +} + /* A set of variables. */ struct var_set -- 2.30.2