25 |
#include "alloc.h" |
#include "alloc.h" |
26 |
#include "case.h" |
#include "case.h" |
27 |
#include "command.h" |
#include "command.h" |
28 |
|
#include "data-in.h" |
29 |
#include "dictionary.h" |
#include "dictionary.h" |
30 |
#include "error.h" |
#include "error.h" |
31 |
#include "lexer.h" |
#include "lexer.h" |
32 |
#include "magic.h" |
#include "magic.h" |
33 |
|
#include "pool.h" |
34 |
|
#include "range-prs.h" |
35 |
#include "str.h" |
#include "str.h" |
36 |
#include "var.h" |
#include "var.h" |
37 |
|
|
41 |
/* Definitions. */ |
/* Definitions. */ |
42 |
|
|
43 |
/* Type of source value for RECODE. */ |
/* Type of source value for RECODE. */ |
44 |
enum |
enum map_in_type |
45 |
{ |
{ |
46 |
RCD_END, /* sentinel value */ |
MAP_SINGLE, /* Specific value. */ |
47 |
RCD_USER, /* user-missing => one */ |
MAP_RANGE, /* Range of values. */ |
48 |
RCD_SINGLE, /* one => one */ |
MAP_SYSMIS, /* System missing value. */ |
49 |
RCD_HIGH, /* x > a => one */ |
MAP_MISSING, /* Any missing value. */ |
50 |
RCD_LOW, /* x < b => one */ |
MAP_ELSE, /* Any value. */ |
51 |
RCD_RANGE, /* b < x < a => one */ |
MAP_CONVERT /* "123" => 123. */ |
|
RCD_ELSE, /* any but SYSMIS => one */ |
|
|
RCD_CONVERT /* "123" => 123 */ |
|
52 |
}; |
}; |
53 |
|
|
54 |
/* Describes how to recode a single value or range of values into a |
/* Describes input values to be mapped. */ |
55 |
single value. */ |
struct map_in |
|
struct coding |
|
56 |
{ |
{ |
57 |
int type; /* RCD_* */ |
enum map_in_type type; /* One of MAP_*. */ |
58 |
union value f1, f2; /* Describe value or range as src. Long |
union value x, y; /* Source values. */ |
|
strings are stored in `c'. */ |
|
|
union value t; /* Describes value as dest. Long strings in `c'. */ |
|
59 |
}; |
}; |
60 |
|
|
61 |
/* Describes how to recode a single variable. */ |
/* Describes the value used as output from a mapping. */ |
62 |
struct rcd_var |
struct map_out |
63 |
{ |
{ |
64 |
struct rcd_var *next; |
bool copy_input; /* If true, copy input to output. */ |
65 |
|
union value value; /* If copy_input false, recoded value. */ |
66 |
unsigned flags; /* RCD_SRC_* | RCD_DEST_* | RCD_MISC_* */ |
int width; /* If copy_input false, output value width. */ |
67 |
|
}; |
|
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). */ |
|
68 |
|
|
69 |
struct coding *map; /* Coding for other values. */ |
/* Describes how to recode a single value or range of values into a |
70 |
size_t nmap, mmap; /* Length of map, max capacity of map. */ |
single value. */ |
71 |
|
struct mapping |
72 |
|
{ |
73 |
|
struct map_in in; /* Input values. */ |
74 |
|
struct map_out out; /* Output value. */ |
75 |
}; |
}; |
76 |
|
|
77 |
/* RECODE transformation. */ |
/* RECODE transformation. */ |
78 |
struct recode_trns |
struct recode_trns |
79 |
{ |
{ |
80 |
struct rcd_var *codings; |
struct pool *pool; |
81 |
|
|
82 |
|
/* Variable types, for convenience. */ |
83 |
|
enum var_type src_type; /* src_vars[*]->type. */ |
84 |
|
enum var_type dst_type; /* dst_vars[*]->type. */ |
85 |
|
|
86 |
|
/* Variables. */ |
87 |
|
struct variable **src_vars; /* Source variables. */ |
88 |
|
struct variable **dst_vars; /* Destination variables. */ |
89 |
|
char **dst_names; /* Name of dest variables, if they're new. */ |
90 |
|
size_t var_cnt; /* Number of variables. */ |
91 |
|
|
92 |
|
/* Mappings. */ |
93 |
|
struct mapping *mappings; /* Value mappings. */ |
94 |
|
size_t map_cnt; /* Number of mappings. */ |
95 |
}; |
}; |
96 |
|
|
97 |
/* What we're recoding from (`src'==`source'). */ |
static bool parse_src_vars (struct recode_trns *); |
98 |
#define RCD_SRC_ERROR 0000u /* Bad value for src. */ |
static bool parse_mappings (struct recode_trns *); |
99 |
#define RCD_SRC_NUMERIC 0001u /* Src is numeric. */ |
static bool parse_dst_vars (struct recode_trns *); |
100 |
#define RCD_SRC_STRING 0002u /* Src is short string. */ |
|
101 |
#define RCD_SRC_MASK 0003u /* AND mask to isolate src bits. */ |
static void add_mapping (struct recode_trns *, |
102 |
|
size_t *map_allocated, const struct map_in *); |
103 |
/* What we're recoding to (`dest'==`destination'). */ |
|
104 |
#define RCD_DEST_ERROR 0000u /* Bad value for dest. */ |
static bool parse_map_in (struct map_in *, struct pool *, |
105 |
#define RCD_DEST_NUMERIC 0004u /* Dest is numeric. */ |
enum var_type src_type, size_t max_src_width); |
106 |
#define RCD_DEST_STRING 0010u /* Dest is short string. */ |
static void set_map_in_generic (struct map_in *, enum map_in_type); |
107 |
#define RCD_DEST_MASK 0014u /* AND mask to isolate dest bits. */ |
static void set_map_in_num (struct map_in *, enum map_in_type, double, double); |
108 |
|
static void set_map_in_str (struct map_in *, struct pool *, |
109 |
/* Miscellaneous bits. */ |
const struct string *, size_t width); |
110 |
#define RCD_MISC_CREATE 0020u /* We create dest var (numeric only) */ |
|
111 |
#define RCD_MISC_DUPLICATE 0040u /* This var_info has the same MAP |
static bool parse_map_out (struct pool *, struct map_out *); |
112 |
value as the previous var_info. |
static void set_map_out_num (struct map_out *, double); |
113 |
Prevents redundant free()ing. */ |
static void set_map_out_str (struct map_out *, struct pool *, |
114 |
#define RCD_MISC_MISSING 0100u /* Encountered MISSING or SYSMIS in |
const struct string *); |
115 |
this input spec. */ |
|
116 |
|
static void enlarge_dst_widths (struct recode_trns *); |
117 |
static int parse_dest_spec (struct rcd_var *rcd, union value *v, |
static void create_dst_vars (struct recode_trns *); |
118 |
size_t *max_dst_width); |
|
|
static int parse_src_spec (struct rcd_var *rcd, int type, size_t max_src_width); |
|
119 |
static trns_proc_func recode_trns_proc; |
static trns_proc_func recode_trns_proc; |
120 |
static trns_free_func recode_trns_free; |
static trns_free_func recode_trns_free; |
|
static double convert_to_double (const char *, int); |
|
121 |
|
|
122 |
/* Parser. */ |
/* Parser. */ |
123 |
|
|
125 |
int |
int |
126 |
cmd_recode (void) |
cmd_recode (void) |
127 |
{ |
{ |
128 |
size_t i; |
do |
129 |
|
{ |
130 |
|
struct recode_trns *trns |
131 |
|
= pool_create_container (struct recode_trns, pool); |
132 |
|
|
133 |
/* Transformation that we're constructing. */ |
/* Parse source variable names, |
134 |
struct rcd_var *rcd; |
then input to output mappings, |
135 |
|
then destintation variable names. */ |
136 |
|
if (!parse_src_vars (trns) |
137 |
|
|| !parse_mappings (trns) |
138 |
|
|| !parse_dst_vars (trns)) |
139 |
|
{ |
140 |
|
recode_trns_free (trns); |
141 |
|
return CMD_PART_SUCCESS; |
142 |
|
} |
143 |
|
|
144 |
|
/* Ensure that all the output strings are at least as wide |
145 |
|
as the widest destination variable. */ |
146 |
|
if (trns->dst_type == ALPHA) |
147 |
|
enlarge_dst_widths (trns); |
148 |
|
|
149 |
|
/* Create destination variables, if needed. |
150 |
|
This must be the final step; otherwise we'd have to |
151 |
|
delete destination variables on failure. */ |
152 |
|
if (trns->src_vars != trns->dst_vars) |
153 |
|
create_dst_vars (trns); |
154 |
|
|
155 |
|
/* Done. */ |
156 |
|
add_transformation (recode_trns_proc, recode_trns_free, trns); |
157 |
|
} |
158 |
|
while (lex_match ('/')); |
159 |
|
|
160 |
|
return lex_end_of_command (); |
161 |
|
} |
162 |
|
|
163 |
/* Type of the src variables. */ |
/* Parses a set of variables to recode into TRNS->src_vars and |
164 |
int type; |
TRNS->var_cnt. Sets TRNS->src_type. Returns true if |
165 |
|
successful, false on parse error. */ |
166 |
|
static bool |
167 |
|
parse_src_vars (struct recode_trns *trns) |
168 |
|
{ |
169 |
|
if (!parse_variables (default_dict, &trns->src_vars, &trns->var_cnt, |
170 |
|
PV_SAME_TYPE)) |
171 |
|
return false; |
172 |
|
pool_register (trns->pool, free, trns->src_vars); |
173 |
|
trns->src_type = trns->src_vars[0]->type; |
174 |
|
return true; |
175 |
|
} |
176 |
|
|
177 |
/* Length of longest src string. */ |
/* Parses a set of mappings, which take the form (input=output), |
178 |
|
into TRNS->mappings and TRNS->map_cnt. Sets TRNS->dst_type. |
179 |
|
Returns true if successful, false on parse error. */ |
180 |
|
static bool |
181 |
|
parse_mappings (struct recode_trns *trns) |
182 |
|
{ |
183 |
size_t max_src_width; |
size_t max_src_width; |
184 |
|
size_t map_allocated; |
185 |
|
bool have_dst_type; |
186 |
|
size_t i; |
187 |
|
|
188 |
|
/* Find length of longest source variable. */ |
189 |
|
max_src_width = trns->src_vars[0]->width; |
190 |
|
for (i = 1; i < trns->var_cnt; i++) |
191 |
|
{ |
192 |
|
size_t var_width = trns->src_vars[i]->width; |
193 |
|
if (var_width > max_src_width) |
194 |
|
max_src_width = var_width; |
195 |
|
} |
196 |
|
|
197 |
|
/* Parse the mappings in parentheses. */ |
198 |
|
trns->mappings = NULL; |
199 |
|
trns->map_cnt = 0; |
200 |
|
map_allocated = 0; |
201 |
|
have_dst_type = false; |
202 |
|
if (!lex_force_match ('(')) |
203 |
|
return false; |
204 |
|
do |
205 |
|
{ |
206 |
|
enum var_type dst_type; |
207 |
|
|
208 |
|
if (!lex_match_id ("CONVERT")) |
209 |
|
{ |
210 |
|
struct map_out out; |
211 |
|
size_t first_map_idx; |
212 |
|
size_t i; |
213 |
|
|
214 |
/* Length of longest dest string. */ |
first_map_idx = trns->map_cnt; |
|
size_t max_dst_width; |
|
215 |
|
|
216 |
/* For stepping through, constructing the linked list of |
/* Parse source specifications. */ |
217 |
recodings. */ |
do |
218 |
struct rcd_var *iter; |
{ |
219 |
|
struct map_in in; |
220 |
/* The real transformation, just a wrapper for a list of |
if (!parse_map_in (&in, trns->pool, |
221 |
rcd_var's. */ |
trns->src_type, max_src_width)) |
222 |
struct recode_trns *trns; |
return false; |
223 |
|
add_mapping (trns, &map_allocated, &in); |
224 |
/* First transformation in the list. rcd is in this list. */ |
lex_match (','); |
225 |
struct rcd_var *head; |
} |
226 |
|
while (!lex_match ('=')); |
|
/* Variables in the current part of the recoding. */ |
|
|
struct variable **v; |
|
|
size_t nv; |
|
|
|
|
|
/* Parses each specification between slashes. */ |
|
|
head = rcd = xmalloc (sizeof *rcd); |
|
|
v = NULL; |
|
|
for (;;) |
|
|
{ |
|
|
/* 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; |
|
227 |
|
|
228 |
/* Parse each coding in parentheses. */ |
if (!parse_map_out (trns->pool, &out)) |
229 |
max_dst_width = 0; |
return false; |
230 |
if (!lex_force_match ('(')) |
dst_type = out.width == 0 ? NUMERIC : ALPHA; |
231 |
goto lossage; |
if (have_dst_type && dst_type != trns->dst_type) |
232 |
for (;;) |
{ |
233 |
{ |
msg (SE, _("Inconsistent target variable types. " |
234 |
/* Get the input value (before the `='). */ |
"Target variables " |
235 |
size_t mark = rcd->nmap; |
"must be all numeric or all string.")); |
236 |
int code = parse_src_spec (rcd, type, max_src_width); |
return false; |
237 |
if (!code) |
} |
238 |
goto lossage; |
|
239 |
|
for (i = first_map_idx; i < trns->map_cnt; i++) |
240 |
/* ELSE is the same as any other input spec except that it |
trns->mappings[i].out = out; |
241 |
precludes later sysmis specifications. */ |
} |
242 |
if (code == 3) |
else |
243 |
{ |
{ |
244 |
had_sysmis = 1; |
/* Parse CONVERT as a special case. */ |
245 |
code = 1; |
struct map_in in; |
246 |
} |
set_map_in_generic (&in, MAP_CONVERT); |
247 |
|
add_mapping (trns, &map_allocated, &in); |
248 |
/* If keyword CONVERT was specified, there is no output |
|
249 |
specification. */ |
dst_type = NUMERIC; |
250 |
if (code == 1) |
if (trns->src_type != ALPHA |
251 |
{ |
|| (have_dst_type && trns->dst_type != NUMERIC)) |
252 |
union value output; |
{ |
253 |
|
msg (SE, _("CONVERT requires string input values and " |
254 |
/* Get the output value (after the `='). */ |
"numeric output values.")); |
255 |
lex_get (); /* Skip `='. */ |
return false; |
256 |
if (!parse_dest_spec (rcd, &output, &max_dst_width)) |
} |
257 |
goto lossage; |
} |
258 |
|
trns->dst_type = dst_type; |
259 |
/* Set the value for SYSMIS if requested and if we don't |
have_dst_type = true; |
|
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 (token != '.') |
|
|
{ |
|
|
lex_error (_("expecting end of command")); |
|
|
goto lossage; |
|
|
} |
|
|
|
|
|
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); |
|
260 |
|
|
261 |
return CMD_SUCCESS; |
if (!lex_force_match (')')) |
262 |
|
return false; |
263 |
|
} |
264 |
|
while (lex_match ('(')); |
265 |
|
|
266 |
lossage: |
return true; |
267 |
free (v); |
} |
268 |
{ |
|
269 |
struct recode_trns t; |
/* Parses a mapping input value into IN, allocating memory from |
270 |
|
POOL. The source value type must be provided as SRC_TYPE and, |
271 |
|
if string, the maximum width of a string source variable must |
272 |
|
be provided in MAX_SRC_WIDTH. Returns true if successful, |
273 |
|
false on parse error. */ |
274 |
|
static bool |
275 |
|
parse_map_in (struct map_in *in, struct pool *pool, |
276 |
|
enum var_type src_type, size_t max_src_width) |
277 |
|
{ |
278 |
|
if (lex_match_id ("ELSE")) |
279 |
|
set_map_in_generic (in, MAP_ELSE); |
280 |
|
else if (src_type == NUMERIC) |
281 |
|
{ |
282 |
|
if (lex_match_id ("MISSING")) |
283 |
|
set_map_in_generic (in, MAP_MISSING); |
284 |
|
else if (lex_match_id ("SYSMIS")) |
285 |
|
set_map_in_generic (in, MAP_SYSMIS); |
286 |
|
else |
287 |
|
{ |
288 |
|
double x, y; |
289 |
|
if (!parse_num_range (&x, &y, NULL)) |
290 |
|
return false; |
291 |
|
set_map_in_num (in, x == y ? MAP_SINGLE : MAP_RANGE, x, y); |
292 |
|
} |
293 |
|
} |
294 |
|
else |
295 |
|
{ |
296 |
|
if (!lex_force_string ()) |
297 |
|
return false; |
298 |
|
set_map_in_str (in, pool, &tokstr, max_src_width); |
299 |
|
lex_get (); |
300 |
|
} |
301 |
|
|
302 |
t.codings = head; |
return true; |
|
recode_trns_free (&t); |
|
|
return CMD_FAILURE; |
|
|
} |
|
303 |
} |
} |
304 |
|
|
305 |
static int |
/* Adds IN to the list of mappings in TRNS. |
306 |
parse_dest_spec (struct rcd_var *rcd, union value *v, size_t *max_dst_width) |
MAP_ALLOCATED is the current number of allocated mappings, |
307 |
|
which is updated as needed. */ |
308 |
|
static void |
309 |
|
add_mapping (struct recode_trns *trns, |
310 |
|
size_t *map_allocated, const struct map_in *in) |
311 |
|
{ |
312 |
|
struct mapping *m; |
313 |
|
if (trns->map_cnt >= *map_allocated) |
314 |
|
trns->mappings = pool_2nrealloc (trns->pool, trns->mappings, |
315 |
|
map_allocated, |
316 |
|
sizeof *trns->mappings); |
317 |
|
m = &trns->mappings[trns->map_cnt++]; |
318 |
|
m->in = *in; |
319 |
|
} |
320 |
|
|
321 |
|
/* Sets IN as a mapping of the given TYPE. */ |
322 |
|
static void |
323 |
|
set_map_in_generic (struct map_in *in, enum map_in_type type) |
324 |
|
{ |
325 |
|
in->type = type; |
326 |
|
} |
327 |
|
|
328 |
|
/* Sets IN as a numeric mapping of the given TYPE, |
329 |
|
with X and Y as the two numeric values. */ |
330 |
|
static void |
331 |
|
set_map_in_num (struct map_in *in, enum map_in_type type, double x, double y) |
332 |
{ |
{ |
333 |
int flags; |
in->type = type; |
334 |
|
in->x.f = x; |
335 |
|
in->y.f = y; |
336 |
|
} |
337 |
|
|
338 |
v->c = NULL; |
/* Sets IN as a string mapping, with STRING as the string, |
339 |
|
allocated from POOL. The string is padded with spaces on the |
340 |
|
right to WIDTH characters long. */ |
341 |
|
static void |
342 |
|
set_map_in_str (struct map_in *in, struct pool *pool, |
343 |
|
const struct string *string, size_t width) |
344 |
|
{ |
345 |
|
in->type = MAP_SINGLE; |
346 |
|
in->x.c = pool_alloc_unaligned (pool, width); |
347 |
|
buf_copy_rpad (in->x.c, width, ds_data (string), ds_length (string)); |
348 |
|
} |
349 |
|
|
350 |
|
/* Parses a mapping output value into OUT, allocating memory from |
351 |
|
POOL. Returns true if successful, false on parse error. */ |
352 |
|
static bool |
353 |
|
parse_map_out (struct pool *pool, struct map_out *out) |
354 |
|
{ |
355 |
if (lex_is_number ()) |
if (lex_is_number ()) |
356 |
{ |
{ |
357 |
v->f = tokval; |
set_map_out_num (out, lex_number ()); |
358 |
lex_get (); |
lex_get (); |
|
flags = RCD_DEST_NUMERIC; |
|
359 |
} |
} |
360 |
else if (lex_match_id ("SYSMIS")) |
else if (lex_match_id ("SYSMIS")) |
361 |
{ |
set_map_out_num (out, SYSMIS); |
|
v->f = SYSMIS; |
|
|
flags = RCD_DEST_NUMERIC; |
|
|
} |
|
362 |
else if (token == T_STRING) |
else if (token == T_STRING) |
363 |
{ |
{ |
364 |
size_t max = *max_dst_width; |
set_map_out_str (out, pool, &tokstr); |
|
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; |
|
365 |
lex_get (); |
lex_get (); |
366 |
} |
} |
367 |
else if (lex_match_id ("COPY")) |
else if (lex_match_id ("COPY")) |
368 |
{ |
out->copy_input = true; |
|
if ((rcd->flags & RCD_SRC_MASK) == RCD_SRC_NUMERIC) |
|
|
{ |
|
|
flags = RCD_DEST_NUMERIC; |
|
|
v->f = -SYSMIS; |
|
|
} |
|
|
else |
|
|
{ |
|
|
flags = RCD_DEST_STRING; |
|
|
v->c = NULL; |
|
|
} |
|
|
} |
|
369 |
else |
else |
370 |
{ |
{ |
371 |
lex_error (_("expecting output value")); |
lex_error (_("expecting output value")); |
372 |
return 0; |
return false; |
373 |
} |
} |
374 |
|
return true; |
375 |
|
} |
376 |
|
|
377 |
if ((rcd->flags & RCD_DEST_MASK) == RCD_DEST_ERROR) |
/* Sets OUT as a numeric mapping output with the given VALUE. */ |
378 |
rcd->flags |= flags; |
static void |
379 |
#if 0 |
set_map_out_num (struct map_out *out, double value) |
|
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; |
|
|
} |
|
|
|
|
|
/* 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) |
|
380 |
{ |
{ |
381 |
struct coding *c; |
out->copy_input = false; |
382 |
|
out->value.f = value; |
383 |
for (;;) |
out->width = 0; |
|
{ |
|
|
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; |
|
384 |
} |
} |
|
|
|
|
/* Data transformation. */ |
|
385 |
|
|
386 |
|
/* Sets OUT as a string mapping output with the given VALUE. */ |
387 |
static void |
static void |
388 |
recode_trns_free (void *t_) |
set_map_out_str (struct map_out *out, struct pool *pool, |
389 |
|
const struct string *value) |
390 |
{ |
{ |
391 |
struct recode_trns *t = t_; |
const char *string = ds_data (value); |
392 |
size_t i; |
size_t length = ds_length (value); |
|
struct rcd_var *head, *next; |
|
393 |
|
|
394 |
head = t->codings; |
out->copy_input = false; |
395 |
while (head) |
out->value.c = pool_alloc_unaligned (pool, length); |
396 |
{ |
memcpy (out->value.c, string, length); |
397 |
if (head->map && !(head->flags & RCD_MISC_DUPLICATE)) |
out->width = length; |
|
{ |
|
|
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; |
|
|
} |
|
|
free (t); |
|
|
} |
|
|
|
|
|
static inline struct coding * |
|
|
find_src_numeric (struct rcd_var *v, struct ccase *c) |
|
|
{ |
|
|
double cmp = case_num (c, v->src->fv); |
|
|
struct coding *cp; |
|
|
|
|
|
if (cmp == SYSMIS) |
|
|
{ |
|
|
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; |
|
|
} |
|
|
|
|
|
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); |
|
|
} |
|
|
} |
|
|
|
|
|
static inline struct coding * |
|
|
find_src_string (struct rcd_var *v, struct ccase *c) |
|
|
{ |
|
|
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); |
|
|
} |
|
398 |
} |
} |
399 |
|
|
400 |
static int |
/* Parses a set of target variables into TRNS->dst_vars and |
401 |
recode_trns_proc (void *t_, struct ccase *c, |
TRNS->dst_names. */ |
402 |
int case_idx UNUSED) |
static bool |
403 |
|
parse_dst_vars (struct recode_trns *trns) |
404 |
{ |
{ |
405 |
struct recode_trns *t = t_; |
size_t i; |
406 |
struct rcd_var *v; |
|
407 |
|
if (lex_match_id ("INTO")) |
|
for (v = t->codings; v; v = v->next) |
|
408 |
{ |
{ |
409 |
struct coding *cp; |
size_t name_cnt; |
410 |
|
size_t i; |
411 |
switch (v->flags & RCD_SRC_MASK) |
|
412 |
{ |
if (!parse_mixed_vars_pool (trns->pool, &trns->dst_names, &name_cnt, |
413 |
case RCD_SRC_NUMERIC: |
PV_NONE)) |
414 |
cp = find_src_numeric (v, c); |
return false; |
415 |
break; |
|
416 |
case RCD_SRC_STRING: |
if (name_cnt != trns->var_cnt) |
417 |
cp = find_src_string (v, c); |
{ |
418 |
break; |
msg (SE, _("%u variable(s) cannot be recoded into " |
419 |
default: |
"%u variable(s). Specify the same number " |
420 |
assert (0); |
"of variables as source and target variables."), |
421 |
abort (); |
(unsigned) trns->var_cnt, (unsigned) name_cnt); |
422 |
} |
return false; |
423 |
if (!cp) |
} |
424 |
continue; |
|
425 |
|
trns->dst_vars = pool_nalloc (trns->pool, |
426 |
/* A matching input value was found. */ |
trns->var_cnt, sizeof *trns->dst_vars); |
427 |
if ((v->flags & RCD_DEST_MASK) == RCD_DEST_NUMERIC) |
for (i = 0; i < trns->var_cnt; i++) |
428 |
{ |
{ |
429 |
double val = cp->t.f; |
struct variable *v; |
430 |
double *out = &case_data_rw (c, v->dest->fv)->f; |
v = trns->dst_vars[i] = dict_lookup_var (default_dict, |
431 |
if (val == -SYSMIS) |
trns->dst_names[i]); |
432 |
*out = case_num (c, v->src->fv); |
if (v == NULL && trns->dst_type == ALPHA) |
|
else |
|
|
*out = val; |
|
|
} |
|
|
else |
|
|
{ |
|
|
char *val = cp->t.c; |
|
|
if (val == NULL) |
|
433 |
{ |
{ |
434 |
if (v->dest->fv != v->src->fv) |
msg (SE, _("There is no variable named " |
435 |
buf_copy_rpad (case_data_rw (c, v->dest->fv)->s, |
"%s. (All string variables specified " |
436 |
v->dest->width, |
"on INTO must already exist. Use the " |
437 |
case_str (c, v->src->fv), v->src->width); |
"STRING command to create a string " |
438 |
|
"variable.)"), |
439 |
|
trns->dst_names[i]); |
440 |
|
return false; |
441 |
} |
} |
442 |
else |
} |
443 |
memcpy (case_data_rw (c, v->dest->fv)->s, cp->t.c, v->dest->width); |
} |
444 |
} |
else |
445 |
|
{ |
446 |
|
trns->dst_vars = trns->src_vars; |
447 |
|
if (trns->src_type != trns->dst_type) |
448 |
|
{ |
449 |
|
msg (SE, _("INTO is required with %s input values " |
450 |
|
"and %s output values."), |
451 |
|
var_type_adj (trns->src_type), |
452 |
|
var_type_adj (trns->dst_type)); |
453 |
|
return false; |
454 |
|
} |
455 |
|
} |
456 |
|
|
457 |
|
for (i = 0; i < trns->var_cnt; i++) |
458 |
|
{ |
459 |
|
struct variable *v = trns->dst_vars[i]; |
460 |
|
if (v != NULL && v->type != trns->dst_type) |
461 |
|
{ |
462 |
|
msg (SE, _("Type mismatch. Cannot store %s data in " |
463 |
|
"%s variable %s."), |
464 |
|
trns->dst_type == ALPHA ? _("string") : _("numeric"), |
465 |
|
v->type == ALPHA ? _("string") : _("numeric"), |
466 |
|
v->name); |
467 |
|
return false; |
468 |
|
} |
469 |
} |
} |
470 |
|
|
471 |
return -1; |
return true; |
472 |
} |
} |
473 |
|
|
474 |
/* Convert NPTR to a `long int' in base 10. Returns the long int on |
/* Ensures that all the output values in TRNS are as wide as the |
475 |
success, NOT_LONG on failure. On success stores a pointer to the |
widest destination variable. */ |
476 |
first character after the number into *ENDPTR. From the GNU C |
static void |
477 |
library. */ |
enlarge_dst_widths (struct recode_trns *trns) |
478 |
static long int |
{ |
479 |
string_to_long (const char *nptr, int width, const char **endptr) |
size_t max_dst_width; |
480 |
{ |
size_t i; |
481 |
int negative; |
|
482 |
unsigned long int cutoff; |
max_dst_width = 0; |
483 |
unsigned int cutlim; |
for (i = 0; i < trns->var_cnt; i++) |
|
unsigned long int i; |
|
|
const char *s; |
|
|
unsigned char c; |
|
|
const char *save; |
|
|
|
|
|
s = nptr; |
|
|
|
|
|
/* Check for a sign. */ |
|
|
if (*s == '-') |
|
484 |
{ |
{ |
485 |
negative = 1; |
struct variable *v = trns->dst_vars[i]; |
486 |
++s; |
if (v->width > max_dst_width) |
487 |
|
max_dst_width = v->width; |
488 |
|
} |
489 |
|
|
490 |
|
for (i = 0; i < trns->map_cnt; i++) |
491 |
|
{ |
492 |
|
struct map_out *out = &trns->mappings[i].out; |
493 |
|
if (!out->copy_input && out->width < max_dst_width) |
494 |
|
{ |
495 |
|
char *s = pool_alloc_unaligned (trns->pool, max_dst_width + 1); |
496 |
|
str_copy_rpad (s, max_dst_width + 1, out->value.c); |
497 |
|
out->value.c = s; |
498 |
|
} |
499 |
} |
} |
500 |
else if (*s == '+') |
} |
501 |
|
|
502 |
|
/* Creates destination variables that don't already exist. */ |
503 |
|
static void |
504 |
|
create_dst_vars (struct recode_trns *trns) |
505 |
|
{ |
506 |
|
size_t i; |
507 |
|
|
508 |
|
for (i = 0; i < trns->var_cnt; i++) |
509 |
{ |
{ |
510 |
negative = 0; |
struct variable **var = &trns->dst_vars[i]; |
511 |
++s; |
const char *name = trns->dst_names[i]; |
512 |
|
|
513 |
|
*var = dict_lookup_var (default_dict, name); |
514 |
|
if (*var == NULL) |
515 |
|
*var = dict_create_var_assert (default_dict, name, 0); |
516 |
|
assert ((*var)->type == trns->dst_type); |
517 |
} |
} |
518 |
else |
} |
519 |
negative = 0; |
|
520 |
if (s >= nptr + width) |
/* Data transformation. */ |
|
return NOT_LONG; |
|
|
|
|
|
/* Save the pointer so we can check later if anything happened. */ |
|
|
save = s; |
|
521 |
|
|
522 |
cutoff = ULONG_MAX / 10ul; |
/* Returns the output mapping in TRNS for an input of VALUE on |
523 |
cutlim = ULONG_MAX % 10ul; |
variable V, or a null pointer if there is no mapping. */ |
524 |
|
static const struct map_out * |
525 |
|
find_src_numeric (struct recode_trns *trns, double value, struct variable *v) |
526 |
|
{ |
527 |
|
struct mapping *m; |
528 |
|
|
529 |
i = 0; |
for (m = trns->mappings; m < trns->mappings + trns->map_cnt; m++) |
|
for (c = *s;;) |
|
530 |
{ |
{ |
531 |
if (isdigit ((unsigned char) c)) |
const struct map_in *in = &m->in; |
532 |
c -= '0'; |
const struct map_out *out = &m->out; |
533 |
else |
bool match; |
534 |
break; |
|
535 |
/* Check for overflow. */ |
switch (in->type) |
536 |
if (i > cutoff || (i == cutoff && c > cutlim)) |
{ |
537 |
return NOT_LONG; |
case MAP_SINGLE: |
538 |
else |
match = value == in->x.f; |
539 |
i = i * 10ul + c; |
break; |
540 |
|
case MAP_MISSING: |
541 |
|
match = mv_is_num_user_missing (&v->miss, value); |
542 |
|
break; |
543 |
|
case MAP_RANGE: |
544 |
|
match = value >= in->x.f && value <= in->y.f; |
545 |
|
break; |
546 |
|
case MAP_ELSE: |
547 |
|
match = true; |
548 |
|
break; |
549 |
|
default: |
550 |
|
abort (); |
551 |
|
} |
552 |
|
|
553 |
s++; |
if (match) |
554 |
if (s >= nptr + width) |
return out; |
|
break; |
|
|
c = *s; |
|
|
} |
|
|
|
|
|
/* 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); |
|
|
} |
|
|
|
|
|
/* 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) |
|
|
{ |
|
|
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; |
|
|
} |
|
|
|
|
|
num = 0.0; |
|
|
got_dot = 0; |
|
|
got_digit = 0; |
|
|
exponent = 0; |
|
|
for (; s < end; ++s) |
|
|
{ |
|
|
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; |
|
555 |
} |
} |
556 |
|
|
557 |
if (!got_digit) |
return NULL; |
558 |
return -SYSMIS; |
} |
559 |
|
|
560 |
if (s < end && (tolower ((unsigned char) (*s)) == 'e' |
/* Returns the output mapping in TRNS for an input of VALUE with |
561 |
|| tolower ((unsigned char) (*s)) == 'd')) |
the given WIDTH, or a null pointer if there is no mapping. */ |
562 |
{ |
static const struct map_out * |
563 |
/* Get the exponent specified after the `e' or `E'. */ |
find_src_string (struct recode_trns *trns, const char *value, int width) |
564 |
long int exp; |
{ |
565 |
|
struct mapping *m; |
566 |
|
|
567 |
s++; |
for (m = trns->mappings; m < trns->mappings + trns->map_cnt; m++) |
568 |
if (s >= end) |
{ |
569 |
return -SYSMIS; |
const struct map_in *in = &m->in; |
570 |
|
struct map_out *out = &m->out; |
571 |
|
bool match; |
572 |
|
|
573 |
|
switch (in->type) |
574 |
|
{ |
575 |
|
case MAP_SINGLE: |
576 |
|
match = !memcmp (value, in->x.c, width); |
577 |
|
break; |
578 |
|
case MAP_ELSE: |
579 |
|
match = true; |
580 |
|
break; |
581 |
|
case MAP_CONVERT: |
582 |
|
{ |
583 |
|
struct data_in di; |
584 |
|
|
585 |
|
di.s = value; |
586 |
|
di.e = value + width; |
587 |
|
di.v = &out->value; |
588 |
|
di.flags = DI_IGNORE_ERROR; |
589 |
|
di.f1 = di.f2 = 0; |
590 |
|
di.format.type = FMT_F; |
591 |
|
di.format.w = width; |
592 |
|
di.format.d = 0; |
593 |
|
match = data_in (&di); |
594 |
|
break; |
595 |
|
} |
596 |
|
default: |
597 |
|
abort (); |
598 |
|
} |
599 |
|
|
600 |
exp = string_to_long (s, end - s, &s); |
if (match) |
601 |
if (exp == NOT_LONG || end == s) |
return out; |
|
return -SYSMIS; |
|
|
exponent += exp; |
|
602 |
} |
} |
603 |
|
|
604 |
while (s < end && isspace ((unsigned char) *s)) |
return NULL; |
605 |
s++; |
} |
|
if (s < end) |
|
|
return -SYSMIS; |
|
|
|
|
|
if (num == 0.0) |
|
|
return 0.0; |
|
606 |
|
|
607 |
/* Multiply NUM by 10 to the EXPONENT power, |
/* Performs RECODE transformation. */ |
608 |
checking for overflow and underflow. */ |
static int |
609 |
|
recode_trns_proc (void *trns_, struct ccase *c, int case_idx UNUSED) |
610 |
|
{ |
611 |
|
struct recode_trns *trns = trns_; |
612 |
|
size_t i; |
613 |
|
|
614 |
if (exponent < 0) |
for (i = 0; i < trns->var_cnt; i++) |
|
{ |
|
|
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) |
|
615 |
{ |
{ |
616 |
if (num > DBL_MAX * pow (10.0, (double) -exponent)) |
struct variable *src_var = trns->src_vars[i]; |
617 |
return -SYSMIS; |
struct variable *dst_var = trns->dst_vars[i]; |
618 |
num *= pow (10.0, (double) exponent); |
|
619 |
|
const union value *src_data = case_data (c, src_var->fv); |
620 |
|
union value *dst_data = case_data_rw (c, dst_var->fv); |
621 |
|
|
622 |
|
const struct map_out *out; |
623 |
|
|
624 |
|
if (trns->src_type == NUMERIC) |
625 |
|
out = find_src_numeric (trns, src_data->f, src_var); |
626 |
|
else |
627 |
|
out = find_src_string (trns, src_data->s, src_var->width); |
628 |
|
|
629 |
|
if (trns->dst_type == NUMERIC) |
630 |
|
{ |
631 |
|
if (out != NULL) |
632 |
|
dst_data->f = !out->copy_input ? out->value.f : src_data->f; |
633 |
|
else if (trns->src_vars != trns->dst_vars) |
634 |
|
dst_data->f = SYSMIS; |
635 |
|
} |
636 |
|
else |
637 |
|
{ |
638 |
|
if (out != NULL) |
639 |
|
{ |
640 |
|
if (!out->copy_input) |
641 |
|
memcpy (dst_data->s, out->value.c, dst_var->width); |
642 |
|
else if (trns->src_vars != trns->dst_vars) |
643 |
|
buf_copy_rpad (dst_data->s, dst_var->width, |
644 |
|
src_data->s, src_var->width); |
645 |
|
} |
646 |
|
else if (trns->src_vars != trns->dst_vars) |
647 |
|
memset (dst_data->s, ' ', dst_var->width); |
648 |
|
} |
649 |
} |
} |
650 |
|
|
651 |
return sign > 0 ? num : -num; |
return -1; |
652 |
|
} |
653 |
|
|
654 |
|
/* Frees a RECODE transformation. */ |
655 |
|
static void |
656 |
|
recode_trns_free (void *trns_) |
657 |
|
{ |
658 |
|
struct recode_trns *trns = trns_; |
659 |
|
pool_destroy (trns->pool); |
660 |
} |
} |