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