Eliminate temp_case, and a few other cleanups.
[pspp-builds.git] / src / format.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
3    Written by Ben Pfaff <blp@gnu.org>.
4
5    This program is free software; you can redistribute it and/or
6    modify it under the terms of the GNU General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful, but
11    WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18    02111-1307, USA. */
19
20 #include <config.h>
21 #include "format.h"
22 #include <ctype.h>
23 #include <assert.h>
24 #include <stdlib.h>
25 #include "error.h"
26 #include "lexer.h"
27 #include "misc.h"
28 #include "str.h"
29
30 #define DEFFMT(LABEL, NAME, N_ARGS, IMIN_W, IMAX_W, OMIN_W, OMAX_W, CAT, \
31                OUTPUT, SPSS_FMT) \
32         {NAME, N_ARGS, IMIN_W, IMAX_W, OMIN_W, OMAX_W, CAT, OUTPUT, SPSS_FMT},
33 struct fmt_desc formats[FMT_NUMBER_OF_FORMATS + 1] =
34 {
35 #include "format.def"
36   {"",         -1, -1,  -1, -1,   -1, 0000, -1, -1},
37 };
38
39 /* Parses the alphabetic prefix of the current token as a format
40    specifier name.  Returns the corresponding format specifier
41    type if successful, or -1 on failure.  If ALLOW_XT is zero,
42    then X and T format specifiers are not allowed.  If CP is
43    nonzero, then *CP is set to the first non-alphabetic character
44    in the current token on success or to a null pointer on
45    failure. */
46 int
47 parse_format_specifier_name (const char **cp, int allow_xt)
48 {
49   char *sp, *ep;
50   int idx;
51
52   sp = ep = ds_value (&tokstr);
53   while (isalpha ((unsigned char) *ep))
54     ep++;
55
56   if (sp != ep) 
57     {
58       /* Find format. */
59       for (idx = 0; idx < FMT_NUMBER_OF_FORMATS; idx++)
60         if (strlen (formats[idx].name) == ep - sp
61             && memcmp (formats[idx].name, sp, ep - sp))
62           break;
63
64       /* Check format. */
65       if (idx < FMT_NUMBER_OF_FORMATS)
66         {
67           if (!allow_xt && (idx == FMT_T || idx == FMT_X)) 
68             {
69               msg (SE, _("X and T format specifiers not allowed here."));
70               idx = -1; 
71             }
72         }
73       else 
74         {
75           /* No match. */
76           msg (SE, _("%.*s is not a valid data format."),
77                (int) (ep - sp), ds_value (&tokstr));
78           idx = -1; 
79         }
80     }
81   else 
82     {
83       lex_error ("expecting data format");
84       idx = -1;
85     }
86       
87   if (cp != NULL)
88     *cp = ep;
89
90   return idx;
91 }
92
93 /* Converts F to its string representation (for instance, "F8.2") and
94    returns a pointer to a static buffer containing that string. */
95 char *
96 fmt_to_string (const struct fmt_spec *f)
97 {
98   static char buf[32];
99
100   if (formats[f->type].n_args >= 2)
101     sprintf (buf, "%s%d.%d", formats[f->type].name, f->w, f->d);
102   else
103     sprintf (buf, "%s%d", formats[f->type].name, f->w);
104   return buf;
105 }
106
107 /* Checks whether SPEC is valid as an input format and returns
108    nonzero if so.  Otherwise, emits an error message and returns
109    zero. */
110 int
111 check_input_specifier (const struct fmt_spec *spec)
112 {
113   struct fmt_desc *f;
114   char *str;
115
116   f = &formats[spec->type];
117   str = fmt_to_string (spec);
118   if (spec->type == FMT_X)
119     return 1;
120   if (f->cat & FCAT_OUTPUT_ONLY)
121     {
122       msg (SE, _("Format %s may not be used as an input format."), f->name);
123       return 0;
124     }
125   if (spec->w < f->Imin_w || spec->w > f->Imax_w)
126     {
127       msg (SE, _("Input format %s specifies a bad width %d.  "
128                  "Format %s requires a width between %d and %d."),
129            str, spec->w, f->name, f->Imin_w, f->Imax_w);
130       return 0;
131     }
132   if ((f->cat & FCAT_EVEN_WIDTH) && spec->w % 2)
133     {
134       msg (SE, _("Input format %s specifies an odd width %d, but "
135                  "format %s requires an even width between %d and "
136                  "%d."), str, spec->w, f->name, f->Imin_w, f->Imax_w);
137       return 0;
138     }
139   if (f->n_args > 1 && (spec->d < 0 || spec->d > 16))
140     {
141       msg (SE, _("Input format %s specifies a bad number of "
142                  "implied decimal places %d.  Input format %s allows "
143                  "up to 16 implied decimal places."), str, spec->d, f->name);
144       return 0;
145     }
146   return 1;
147 }
148
149 /* Checks whether SPEC is valid as an output format and returns
150    nonzero if so.  Otherwise, emits an error message and returns
151    zero. */
152 int
153 check_output_specifier (const struct fmt_spec *spec)
154 {
155   struct fmt_desc *f;
156   char *str;
157
158   f = &formats[spec->type];
159   str = fmt_to_string (spec);
160   if (spec->type == FMT_X)
161     return 1;
162   if (spec->w < f->Omin_w || spec->w > f->Omax_w)
163     {
164       msg (SE, _("Output format %s specifies a bad width %d.  "
165                  "Format %s requires a width between %d and %d."),
166            str, spec->w, f->name, f->Omin_w, f->Omax_w);
167       return 0;
168     }
169   if (spec->d > 1
170       && (spec->type == FMT_F || spec->type == FMT_COMMA
171           || spec->type == FMT_DOLLAR)
172       && spec->w < f->Omin_w + 1 + spec->d)
173     {
174       msg (SE, _("Output format %s requires minimum width %d to allow "
175                  "%d decimal places.  Try %s%d.%d instead of %s."),
176            f->name, f->Omin_w + 1 + spec->d, spec->d, f->name,
177            f->Omin_w + 1 + spec->d, spec->d, str);
178       return 0;
179     }
180   if ((f->cat & FCAT_EVEN_WIDTH) && spec->w % 2)
181     {
182       msg (SE, _("Output format %s specifies an odd width %d, but "
183                  "output format %s requires an even width between %d and "
184                  "%d."), str, spec->w, f->name, f->Omin_w, f->Omax_w);
185       return 0;
186     }
187   if (f->n_args > 1 && (spec->d < 0 || spec->d > 16))
188     {
189       msg (SE, _("Output format %s specifies a bad number of "
190                  "implied decimal places %d.  Output format %s allows "
191                  "a number of implied decimal places between 1 "
192                  "and 16."), str, spec->d, f->name);
193       return 0;
194     }
195   return 1;
196 }
197
198 /* If a string variable has width W, you can't display it with a
199    format specifier with a required width MIN_LEN>W. */
200 int
201 check_string_specifier (const struct fmt_spec *f, int min_len)
202 {
203   if ((f->type == FMT_A && min_len > f->w)
204       || (f->type == FMT_AHEX && min_len * 2 > f->w))
205     {
206       msg (SE, _("Can't display a string variable of width %d with "
207                  "format specifier %s."), min_len, fmt_to_string (f));
208       return 0;
209     }
210   return 1;
211 }
212
213 /* Converts input format specifier INPUT into output format
214    specifier OUTPUT. */
215 void
216 convert_fmt_ItoO (const struct fmt_spec *input, struct fmt_spec *output)
217 {
218   output->type = formats[input->type].output;
219   output->w = input->w;
220   if (output->w > formats[output->type].Omax_w)
221     output->w = formats[output->type].Omax_w;
222   output->d = input->d;
223
224   switch (input->type)
225     {
226     case FMT_F:
227     case FMT_N:
228       if (output->d > 1 && output->w < 2 + output->d)
229         output->w = 2 + output->d;
230       break;
231     case FMT_E:
232       output->w = max (max (input->w, input->d+7), 10);
233       output->d = max (input->d, 3);
234       break;
235     case FMT_COMMA:
236     case FMT_DOT:
237       /* nothing is necessary */
238       break;
239     case FMT_DOLLAR:
240     case FMT_PCT:
241       if (output->w < 2)
242         output->w = 2;
243       break;
244     case FMT_PIBHEX:
245       {
246         static const int map[] = {4, 6, 9, 11, 14, 16, 18, 21};
247         assert (input->w % 2 == 0 && input->w >= 2 && input->w <= 16);
248         output->w = map[input->w / 2 - 1];
249         break;
250       }
251     case FMT_RBHEX:
252       output->w = 8, output->d = 2;     /* FIXME */
253       break;
254     case FMT_IB:
255     case FMT_PIB:
256     case FMT_P:
257     case FMT_PK:
258     case FMT_RB:
259       if (input->d < 1)
260         output->w = 8, output->d = 2;
261       else
262         output->w = 9 + input->d;
263       break;
264     case FMT_CCA:
265     case FMT_CCB:
266     case FMT_CCC:
267     case FMT_CCD:
268     case FMT_CCE:
269       assert (0);
270     case FMT_Z:
271     case FMT_A:
272       /* nothing is necessary */
273       break;
274     case FMT_AHEX:
275       output->w = input->w / 2;
276       break;
277     case FMT_DATE:
278     case FMT_EDATE:
279     case FMT_SDATE:
280     case FMT_ADATE:
281     case FMT_JDATE:
282       /* nothing is necessary */
283       break;
284     case FMT_QYR:
285       if (output->w < 6)
286         output->w = 6;
287       break;
288     case FMT_MOYR:
289       /* nothing is necessary */
290       break;
291     case FMT_WKYR:
292       if (output->w < 8)
293         output->w = 8;
294       break;
295     case FMT_TIME:
296     case FMT_DTIME:
297     case FMT_DATETIME:
298     case FMT_WKDAY:
299     case FMT_MONTH:
300       /* nothing is necessary */
301       break;
302     default:
303       assert (0);
304     }
305 }
306
307 /* Parses a format specifier from the token stream and returns
308    nonzero only if successful.  Emits an error message on
309    failure.  Allows X and T format specifiers only if ALLOW_XT is
310    nonzero.  The caller should call check_input_specifier() or
311    check_output_specifier() on the parsed format as
312    necessary.  */
313 int
314 parse_format_specifier (struct fmt_spec *input, int allow_xt)
315 {
316   struct fmt_spec spec;
317   struct fmt_desc *f;
318   const char *cp;
319   char *cp2;
320   int type, w, d;
321
322   if (token != T_ID)
323     {
324       msg (SE, _("Format specifier expected."));
325       return 0;
326     }
327   type = parse_format_specifier_name (&cp, allow_xt);
328   if (type == -1)
329     return 0;
330   f = &formats[type];
331
332   w = strtol (cp, &cp2, 10);
333   if (cp2 == cp && type != FMT_X)
334     {
335       msg (SE, _("Data format %s does not specify a width."),
336            ds_value (&tokstr));
337       return 0;
338     }
339
340   cp = cp2;
341   if (f->n_args > 1 && *cp == '.')
342     {
343       cp++;
344       d = strtol (cp, &cp2, 10);
345       cp = cp2;
346     }
347   else
348     d = 0;
349
350   if (*cp)
351     {
352       msg (SE, _("Data format %s is not valid."), ds_value (&tokstr));
353       return 0;
354     }
355   lex_get ();
356
357   spec.type = type;
358   spec.w = w;
359   spec.d = d;
360   *input = spec;
361
362   return 1;
363 }
364
365 /* Returns the width corresponding to the format specifier.  The
366    return value is the value of the `width' member of a `struct
367    variable' for such an input format. */
368 int
369 get_format_var_width (const struct fmt_spec *spec) 
370 {
371   if (spec->type == FMT_AHEX)
372     return spec->w * 2;
373   else if (spec->type == FMT_A)
374     return spec->w;
375   else
376     return 0;
377 }
378
379 /* Returns the PSPP format corresponding to the given SPSS
380    format. */
381 int
382 translate_fmt (int spss) 
383 {
384   int type;
385   
386   for (type = 0; type < FMT_NUMBER_OF_FORMATS; type++)
387     if (formats[type].spss == spss)
388       return type;
389   return -1;
390 }