/[pspp]/pspp/src/recode.c
ViewVC logotype

Diff of /pspp/src/recode.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.29 by blp, Thu Nov 3 06:21:46 2005 UTC revision 1.30 by blp, Sat Nov 5 03:45:28 2005 UTC
# Line 25  Line 25 
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    
# Line 38  Line 41 
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    
# Line 117  static double convert_to_double (const c Line 125  static double convert_to_double (const c
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  }  }

Legend:
Removed from v.1.29  
changed lines
  Added in v.1.30

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26