Beginning of VFM cleanup.
[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 const int translate_fmt[40] =
40   {
41     -1, FMT_A, FMT_AHEX, FMT_COMMA, FMT_DOLLAR, FMT_F, FMT_IB,
42     FMT_PIBHEX, FMT_P, FMT_PIB, FMT_PK, FMT_RB, FMT_RBHEX, -1,
43     -1, FMT_Z, FMT_N, FMT_E, -1, -1, FMT_DATE, FMT_TIME,
44     FMT_DATETIME, FMT_ADATE, FMT_JDATE, FMT_DTIME, FMT_WKDAY,
45     FMT_MONTH, FMT_MOYR, FMT_QYR, FMT_WKYR, FMT_PCT, FMT_DOT,
46     FMT_CCA, FMT_CCB, FMT_CCC, FMT_CCD, FMT_CCE, FMT_EDATE,
47     FMT_SDATE,
48   };
49
50 int
51 parse_format_specifier_name (const char **cp, int allow_xt)
52 {
53   struct fmt_desc *f;
54   char *ep;
55   int x;
56
57   ep = ds_value (&tokstr);
58   while (isalpha ((unsigned char) *ep))
59     ep++;
60   x = *ep;
61   *ep = 0;
62
63   for (f = formats; f->name[0]; f++)
64     if (!strcmp (f->name, ds_value (&tokstr)))
65       {
66         int indx = f - formats;
67
68         *ep = x;
69         if (cp)
70           *cp = ep;
71
72         if (!allow_xt && (indx == FMT_T || indx == FMT_X))
73           {
74             msg (SE, _("X and T format specifiers not allowed here."));
75             return -1;
76           }
77         return indx;
78       }
79
80   msg (SE, _("%s is not a valid data format."), ds_value (&tokstr));
81   *ep = x;
82   return -1;
83 }
84
85 /* Converts F to its string representation (for instance, "F8.2") and
86    returns a pointer to a static buffer containing that string. */
87 char *
88 fmt_to_string (const struct fmt_spec *f)
89 {
90   static char buf[32];
91
92   if (formats[f->type].n_args >= 2)
93     sprintf (buf, "%s%d.%d", formats[f->type].name, f->w, f->d);
94   else
95     sprintf (buf, "%s%d", formats[f->type].name, f->w);
96   return buf;
97 }
98
99 int
100 check_input_specifier (const struct fmt_spec *spec)
101 {
102   struct fmt_desc *f;
103   char *str;
104
105   f = &formats[spec->type];
106   str = fmt_to_string (spec);
107   if (spec->type == FMT_X)
108     return 1;
109   if (f->cat & FCAT_OUTPUT_ONLY)
110     {
111       msg (SE, _("Format %s may not be used as an input format."), f->name);
112       return 0;
113     }
114   if (spec->w < f->Imin_w || spec->w > f->Imax_w)
115     {
116       msg (SE, _("Input format %s specifies a bad width %d.  "
117                  "Format %s requires a width between %d and %d."),
118            str, spec->w, f->name, f->Imin_w, f->Imax_w);
119       return 0;
120     }
121   if ((f->cat & FCAT_EVEN_WIDTH) && spec->w % 2)
122     {
123       msg (SE, _("Input format %s specifies an odd width %d, but "
124                  "format %s requires an even width between %d and "
125                  "%d."), str, spec->w, f->name, f->Imin_w, f->Imax_w);
126       return 0;
127     }
128   if (f->n_args > 1 && (spec->d < 0 || spec->d > 16))
129     {
130       msg (SE, _("Input format %s specifies a bad number of "
131                  "implied decimal places %d.  Input format %s allows "
132                  "up to 16 implied decimal places."), str, spec->d, f->name);
133       return 0;
134     }
135   return 1;
136 }
137
138 int
139 check_output_specifier (const struct fmt_spec *spec)
140 {
141   struct fmt_desc *f;
142   char *str;
143
144   f = &formats[spec->type];
145   str = fmt_to_string (spec);
146   if (spec->type == FMT_X)
147     return 1;
148   if (spec->w < f->Omin_w || spec->w > f->Omax_w)
149     {
150       msg (SE, _("Output format %s specifies a bad width %d.  "
151                  "Format %s requires a width between %d and %d."),
152            str, spec->w, f->name, f->Omin_w, f->Omax_w);
153       return 0;
154     }
155   if (spec->d > 1
156       && (spec->type == FMT_F || spec->type == FMT_COMMA
157           || spec->type == FMT_DOLLAR)
158       && spec->w < f->Omin_w + 1 + spec->d)
159     {
160       msg (SE, _("Output format %s requires minimum width %d to allow "
161                  "%d decimal places.  Try %s%d.%d instead of %s."),
162            f->name, f->Omin_w + 1 + spec->d, spec->d, f->name,
163            f->Omin_w + 1 + spec->d, spec->d, str);
164       return 0;
165     }
166   if ((f->cat & FCAT_EVEN_WIDTH) && spec->w % 2)
167     {
168       msg (SE, _("Output format %s specifies an odd width %d, but "
169                  "output format %s requires an even width between %d and "
170                  "%d."), str, spec->w, f->name, f->Omin_w, f->Omax_w);
171       return 0;
172     }
173   if (f->n_args > 1 && (spec->d < 0 || spec->d > 16))
174     {
175       msg (SE, _("Output format %s specifies a bad number of "
176                  "implied decimal places %d.  Output format %s allows "
177                  "a number of implied decimal places between 1 "
178                  "and 16."), str, spec->d, f->name);
179       return 0;
180     }
181   return 1;
182 }
183
184 /* If a string variable has width W, you can't display it with a
185    format specifier with a required width MIN_LEN>W. */
186 int
187 check_string_specifier (const struct fmt_spec *f, int min_len)
188 {
189   if ((f->type == FMT_A && min_len > f->w)
190       || (f->type == FMT_AHEX && min_len * 2 > f->w))
191     {
192       msg (SE, _("Can't display a string variable of width %d with "
193                  "format specifier %s."), min_len, fmt_to_string (f));
194       return 0;
195     }
196   return 1;
197 }
198
199 void
200 convert_fmt_ItoO (const struct fmt_spec *input, struct fmt_spec *output)
201 {
202   output->type = formats[input->type].output;
203   output->w = input->w;
204   if (output->w > formats[output->type].Omax_w)
205     output->w = formats[output->type].Omax_w;
206   output->d = input->d;
207
208   switch (input->type)
209     {
210     case FMT_F:
211     case FMT_N:
212       if (output->d > 1 && output->w < 2 + output->d)
213         output->w = 2 + output->d;
214       break;
215     case FMT_E:
216       output->w = max (max (input->w, input->d+7), 10);
217       output->d = max (input->d, 3);
218       break;
219     case FMT_COMMA:
220     case FMT_DOT:
221       /* nothing is necessary */
222       break;
223     case FMT_DOLLAR:
224     case FMT_PCT:
225       if (output->w < 2)
226         output->w = 2;
227       break;
228     case FMT_PIBHEX:
229       {
230         static const int map[] = {4, 6, 9, 11, 14, 16, 18, 21};
231         assert (input->w % 2 == 0 && input->w >= 2 && input->w <= 16);
232         output->w = map[input->w / 2 - 1];
233         break;
234       }
235     case FMT_RBHEX:
236       output->w = 8, output->d = 2;     /* FIXME */
237       break;
238     case FMT_IB:
239     case FMT_PIB:
240     case FMT_P:
241     case FMT_PK:
242     case FMT_RB:
243       if (input->d < 1)
244         output->w = 8, output->d = 2;
245       else
246         output->w = 9 + input->d;
247       break;
248     case FMT_CCA:
249     case FMT_CCB:
250     case FMT_CCC:
251     case FMT_CCD:
252     case FMT_CCE:
253       assert (0);
254     case FMT_Z:
255     case FMT_A:
256       /* nothing is necessary */
257       break;
258     case FMT_AHEX:
259       output->w = input->w / 2;
260       break;
261     case FMT_DATE:
262     case FMT_EDATE:
263     case FMT_SDATE:
264     case FMT_ADATE:
265     case FMT_JDATE:
266       /* nothing is necessary */
267       break;
268     case FMT_QYR:
269       if (output->w < 6)
270         output->w = 6;
271       break;
272     case FMT_MOYR:
273       /* nothing is necessary */
274       break;
275     case FMT_WKYR:
276       if (output->w < 8)
277         output->w = 8;
278       break;
279     case FMT_TIME:
280     case FMT_DTIME:
281     case FMT_DATETIME:
282     case FMT_WKDAY:
283     case FMT_MONTH:
284       /* nothing is necessary */
285       break;
286     default:
287       assert (0);
288     }
289 }
290
291 int
292 parse_format_specifier (struct fmt_spec *input, int allow_xt)
293 {
294   struct fmt_spec spec;
295   struct fmt_desc *f;
296   const char *cp;
297   char *cp2;
298   int type, w, d;
299
300   if (token != T_ID)
301     {
302       msg (SE, _("Format specifier expected."));
303       return 0;
304     }
305   type = parse_format_specifier_name (&cp, allow_xt);
306   if (type == -1)
307     return 0;
308   f = &formats[type];
309
310   w = strtol (cp, &cp2, 10);
311   if (cp2 == cp && type != FMT_X)
312     {
313       msg (SE, _("Data format %s does not specify a width."),
314            ds_value (&tokstr));
315       return 0;
316     }
317
318   cp = cp2;
319   if (f->n_args > 1 && *cp == '.')
320     {
321       cp++;
322       d = strtol (cp, &cp2, 10);
323       cp = cp2;
324     }
325   else
326     d = 0;
327
328   if (*cp)
329     {
330       msg (SE, _("Data format %s is not valid."), ds_value (&tokstr));
331       return 0;
332     }
333   lex_get ();
334
335   spec.type = type;
336   spec.w = w;
337   spec.d = d;
338   *input = spec;
339
340   return 1;
341 }
342
343 int
344 get_format_var_width (const struct fmt_spec *spec) 
345 {
346   if (spec->type == FMT_AHEX)
347     return spec->w * 2;
348   else if (spec->type == FMT_A)
349     return spec->w;
350   else
351     return 0;
352 }