5e06ab2291e43ff4f40efcaeb2fbae4bcfe2a569
[pspp] / 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., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, USA. */
19
20 #include <config.h>
21 #include "format.h"
22 #include <ctype.h>
23 #include "error.h"
24 #include <stdlib.h>
25 #include "error.h"
26 #include "lexer.h"
27 #include "misc.h"
28 #include "str.h"
29 #include "var.h"
30
31 #define DEFFMT(LABEL, NAME, N_ARGS, IMIN_W, IMAX_W, OMIN_W, OMAX_W, CAT, \
32                OUTPUT, SPSS_FMT) \
33         {NAME, N_ARGS, IMIN_W, IMAX_W, OMIN_W, OMAX_W, CAT, OUTPUT, SPSS_FMT},
34 struct fmt_desc formats[FMT_NUMBER_OF_FORMATS + 1] =
35 {
36 #include "format.def"
37   {"",         -1, -1,  -1, -1,   -1, 0000, -1, -1},
38 };
39
40 /* Common formats. */
41 const struct fmt_spec f8_2 = {FMT_F, 8, 2};
42
43 /* Parses the alphabetic prefix of the current token as a format
44    specifier name.  Returns the corresponding format specifier
45    type if successful, or -1 on failure.  If ALLOW_XT is zero,
46    then X and T format specifiers are not allowed.  If CP is
47    nonzero, then *CP is set to the first non-alphabetic character
48    in the current token on success or to a null pointer on
49    failure. */
50 int
51 parse_format_specifier_name (const char **cp, enum fmt_parse_flags flags)
52 {
53   char *sp, *ep;
54   int idx;
55
56   sp = ep = ds_c_str (&tokstr);
57   while (isalpha ((unsigned char) *ep))
58     ep++;
59
60   if (sp != ep) 
61     {
62       /* Find format. */
63       for (idx = 0; idx < FMT_NUMBER_OF_FORMATS; idx++)
64         if (strlen (formats[idx].name) == ep - sp
65             && !buf_compare_case (formats[idx].name, sp, ep - sp))
66           break;
67
68       /* Check format. */
69       if (idx < FMT_NUMBER_OF_FORMATS)
70         {
71           if (!(flags & FMTP_ALLOW_XT) && (idx == FMT_T || idx == FMT_X)) 
72             {
73               if (!(flags & FMTP_SUPPRESS_ERRORS))
74                 msg (SE, _("X and T format specifiers not allowed here."));
75               idx = -1; 
76             }
77         }
78       else 
79         {
80           /* No match. */
81           if (!(flags & FMTP_SUPPRESS_ERRORS))
82             msg (SE, _("%.*s is not a valid data format."),
83                  (int) (ep - sp), ds_c_str (&tokstr));
84           idx = -1; 
85         }
86     }
87   else 
88     {
89       lex_error ("expecting data format");
90       idx = -1;
91     }
92       
93   if (cp != NULL) 
94     {
95       if (idx != -1)
96         *cp = ep;
97       else
98         *cp = NULL;
99     }
100
101   return idx;
102 }
103
104 /* Converts F to its string representation (for instance, "F8.2") and
105    returns a pointer to a static buffer containing that string. */
106 char *
107 fmt_to_string (const struct fmt_spec *f)
108 {
109   static char buf[32];
110
111   if (formats[f->type].n_args >= 2)
112     sprintf (buf, "%s%d.%d", formats[f->type].name, f->w, f->d);
113   else
114     sprintf (buf, "%s%d", formats[f->type].name, f->w);
115   return buf;
116 }
117
118 /* Does checks in common betwen check_input_specifier() and
119    check_output_specifier() and returns true if so.  Otherwise,
120    emits an error message (if EMIT_ERROR is nonzero) and returns
121    false. */
122 static bool
123 check_common_specifier (const struct fmt_spec *spec, bool emit_error)
124 {
125   struct fmt_desc *f = &formats[spec->type];
126   char *str = fmt_to_string (spec);
127
128   if ((f->cat & FCAT_EVEN_WIDTH) && spec->w % 2)
129     {
130       if (emit_error)
131         msg (SE, _("Format %s specifies an odd width %d, but "
132                    "format %s requires an even width."),
133              str, spec->w, f->name, f->Imin_w, f->Imax_w);
134       return false;
135     }
136   if (f->n_args > 1 && (spec->d < 0 || spec->d > 16))
137     {
138       if (emit_error)
139         msg (SE, _("Format %s specifies a bad number of "
140                    "implied decimal places %d.  Input format %s allows "
141                    "up to 16 implied decimal places."), str, spec->d, f->name);
142       return false;
143     }
144   return true;
145 }
146
147 /* Checks whether SPEC is valid as an input format and returns
148    nonzero if so.  Otherwise, emits an error message (if
149    EMIT_ERROR is nonzero) and returns zero. */
150 int
151 check_input_specifier (const struct fmt_spec *spec, int emit_error)
152 {
153   struct fmt_desc *f = &formats[spec->type];
154   char *str = fmt_to_string (spec);
155
156   if (!check_common_specifier (spec, emit_error))
157     return false;
158   if (spec->type == FMT_X)
159     return 1;
160   if (f->cat & FCAT_OUTPUT_ONLY)
161     {
162       if (emit_error)
163         msg (SE, _("Format %s may not be used for input."), f->name);
164       return 0;
165     }
166   if (spec->w < f->Imin_w || spec->w > f->Imax_w)
167     {
168       if (emit_error)
169         msg (SE, _("Input format %s specifies a bad width %d.  "
170                    "Format %s requires a width between %d and %d."),
171              str, spec->w, f->name, f->Imin_w, f->Imax_w);
172       return 0;
173     }
174   if ((spec->type == FMT_F || spec->type == FMT_COMMA
175           || spec->type == FMT_DOLLAR)
176       && spec->d > spec->w)
177     {
178       if (emit_error)
179         msg (SE, _("Input format %s is invalid because it specifies more "
180                    "decimal places than the field width."), str);
181       return 0;
182     }
183   return 1;
184 }
185
186 /* Checks whether SPEC is valid as an output format and returns
187    nonzero if so.  Otherwise, emits an error message (if
188    EMIT_ERROR is nonzero) and returns zero. */
189 int
190 check_output_specifier (const struct fmt_spec *spec, int emit_error)
191 {
192   struct fmt_desc *f = &formats[spec->type];
193   char *str = fmt_to_string (spec);
194
195   if (!check_common_specifier (spec, emit_error))
196     return false;
197   if (spec->type == FMT_X)
198     return 1;
199   if (spec->w < f->Omin_w || spec->w > f->Omax_w)
200     {
201       if (emit_error)
202         msg (SE, _("Output format %s specifies a bad width %d.  "
203                    "Format %s requires a width between %d and %d."),
204              str, spec->w, f->name, f->Omin_w, f->Omax_w);
205       return 0;
206     }
207   if ((spec->type == FMT_F || spec->type == FMT_COMMA
208           || spec->type == FMT_DOLLAR)
209       && spec->d >= spec->w)
210     {
211       if (emit_error)
212         msg (SE, _("Output format %s is invalid because it specifies as "
213                    "many decimal places as the field width, which "
214                    "fails to allow space for a decimal point.  "
215                    "Try %s%d.%d instead."),
216              str, f->name, f->name, spec->d + 1, spec->d);
217       return 0;
218     }
219   return 1;
220 }
221
222 /* Checks that FORMAT is appropriate for a variable of the given
223    TYPE and returns true if so.  Otherwise returns false and (if
224    EMIT_ERROR is true) emits an error message. */
225 bool
226 check_specifier_type (const struct fmt_spec *format,
227                       int type, bool emit_error) 
228 {
229   const struct fmt_desc *f = &formats[format->type];
230   assert (type == NUMERIC || type == ALPHA);
231   if ((type == ALPHA) != ((f->cat & FCAT_STRING) != 0))
232     {
233       if (emit_error)
234         msg (SE, _("%s variables are not compatible with %s format %s."),
235              type == ALPHA ? _("String") : _("Numeric"),
236              type == ALPHA ? _("numeric") : _("string"),
237              fmt_to_string (format));
238       return false;
239     }
240   return true;
241 }
242   
243 /* Checks that FORMAT is appropriate for a variable of the given
244    WIDTH and returns true if so.  Otherwise returns false and (if
245    EMIT_ERROR is true) emits an error message. */
246 bool
247 check_specifier_width (const struct fmt_spec *format,
248                        int width, bool emit_error) 
249 {
250   if (!check_specifier_type (format, width != 0 ? ALPHA : NUMERIC, emit_error))
251     return false;
252   if (get_format_var_width (format) != width)
253     {
254       if (emit_error)
255         msg (SE, _("String variable with width %d not compatible with "
256                    "format %s."),
257              width, fmt_to_string (format));
258       return false;
259     }
260   return true;
261 }
262
263 /* Converts input format specifier INPUT into output format
264    specifier OUTPUT. */
265 void
266 convert_fmt_ItoO (const struct fmt_spec *input, struct fmt_spec *output)
267 {
268   assert (check_input_specifier (input, 0));
269
270   output->type = formats[input->type].output;
271   output->w = input->w;
272   if (output->w > formats[output->type].Omax_w)
273     output->w = formats[output->type].Omax_w;
274   output->d = input->d;
275
276   switch (input->type)
277     {
278     case FMT_F:
279     case FMT_N:
280       if (output->d > 0)
281         output->w++;
282       break;
283     case FMT_E:
284       output->w = max (max (input->w, input->d+7), 10);
285       output->d = max (input->d, 3);
286       break;
287     case FMT_COMMA:
288     case FMT_DOT:
289       /* nothing is necessary */
290       break;
291     case FMT_DOLLAR:
292     case FMT_PCT:
293       if (output->w < 2)
294         output->w = 2;
295       break;
296     case FMT_PIBHEX:
297       {
298         static const int map[] = {4, 6, 9, 11, 14, 16, 18, 21};
299         assert (input->w % 2 == 0 && input->w >= 2 && input->w <= 16);
300         output->w = map[input->w / 2 - 1];
301         break;
302       }
303     case FMT_RBHEX:
304       output->w = 8, output->d = 2;     /* FIXME */
305       break;
306     case FMT_IB:
307     case FMT_PIB:
308     case FMT_P:
309     case FMT_PK:
310     case FMT_RB:
311       if (input->d < 1)
312         output->w = 8, output->d = 2;
313       else
314         output->w = 9 + input->d;
315       break;
316     case FMT_CCA:
317     case FMT_CCB:
318     case FMT_CCC:
319     case FMT_CCD:
320     case FMT_CCE:
321       assert (0);
322     case FMT_Z:
323     case FMT_A:
324       /* nothing is necessary */
325       break;
326     case FMT_AHEX:
327       output->w = input->w / 2;
328       break;
329     case FMT_DATE:
330     case FMT_EDATE:
331     case FMT_SDATE:
332     case FMT_ADATE:
333     case FMT_JDATE:
334       /* nothing is necessary */
335       break;
336     case FMT_QYR:
337       if (output->w < 6)
338         output->w = 6;
339       break;
340     case FMT_MOYR:
341       /* nothing is necessary */
342       break;
343     case FMT_WKYR:
344       if (output->w < 8)
345         output->w = 8;
346       break;
347     case FMT_TIME:
348     case FMT_DTIME:
349     case FMT_DATETIME:
350     case FMT_WKDAY:
351     case FMT_MONTH:
352       /* nothing is necessary */
353       break;
354     default:
355       assert (0);
356     }
357
358   assert (check_output_specifier (output, 0));
359 }
360
361 /* Parses a format specifier from the token stream and returns
362    nonzero only if successful.  Emits an error message on
363    failure.  Allows X and T format specifiers only if ALLOW_XT is
364    nonzero.  The caller should call check_input_specifier() or
365    check_output_specifier() on the parsed format as
366    necessary.  */
367 int
368 parse_format_specifier (struct fmt_spec *input, enum fmt_parse_flags flags)
369 {
370   struct fmt_spec spec;
371   struct fmt_desc *f;
372   const char *cp;
373   char *cp2;
374   int type, w, d;
375
376   if (token != T_ID)
377     {
378       if (!(flags & FMTP_SUPPRESS_ERRORS))
379         msg (SE, _("Format specifier expected."));
380       return 0;
381     }
382   type = parse_format_specifier_name (&cp, flags);
383   if (type == -1)
384     return 0;
385   f = &formats[type];
386
387   w = strtol (cp, &cp2, 10);
388   if (cp2 == cp && type != FMT_X)
389     {
390       if (!(flags & FMTP_SUPPRESS_ERRORS))
391         msg (SE, _("Data format %s does not specify a width."),
392              ds_c_str (&tokstr));
393       return 0;
394     }
395
396   cp = cp2;
397   if (f->n_args > 1 && *cp == '.')
398     {
399       cp++;
400       d = strtol (cp, &cp2, 10);
401       cp = cp2;
402     }
403   else
404     d = 0;
405
406   if (*cp)
407     {
408       if (!(flags & FMTP_SUPPRESS_ERRORS))
409         msg (SE, _("Data format %s is not valid."), ds_c_str (&tokstr));
410       return 0;
411     }
412   lex_get ();
413
414   spec.type = type;
415   spec.w = w;
416   spec.d = d;
417   *input = spec;
418
419   return 1;
420 }
421
422 /* Returns the width corresponding to the format specifier.  The
423    return value is the value of the `width' member of a `struct
424    variable' for such an input format. */
425 int
426 get_format_var_width (const struct fmt_spec *spec) 
427 {
428   if (spec->type == FMT_AHEX)
429     return spec->w / 2;
430   else if (spec->type == FMT_A)
431     return spec->w;
432   else
433     return 0;
434 }
435
436 /* Returns the PSPP format corresponding to the given SPSS
437    format. */
438 int
439 translate_fmt (int spss) 
440 {
441   int type;
442   
443   for (type = 0; type < FMT_NUMBER_OF_FORMATS; type++)
444     if (formats[type].spss == spss)
445       return type;
446   return -1;
447 }
448
449 /* Returns an input format specification with type TYPE, width W,
450    and D decimals. */
451 struct fmt_spec
452 make_input_format (int type, int w, int d) 
453 {
454   struct fmt_spec f;
455   f.type = type;
456   f.w = w;
457   f.d = d;
458   assert (check_input_specifier (&f, 0));
459   return f;
460 }
461
462 /* Returns an output format specification with type TYPE, width
463    W, and D decimals. */
464 struct fmt_spec
465 make_output_format (int type, int w, int d)
466 {
467   struct fmt_spec f;
468   f.type = type;
469   f.w = w;
470   f.d = d;
471   assert (check_output_specifier (&f, 0));
472   return f;
473 }