744cd6feccaf3644fb118cc9cdfb0c51c4f6a3f3
[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 "error.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_c_str (&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_c_str (&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     {
89       if (idx != -1)
90         *cp = ep;
91       else
92         *cp = NULL;
93     }
94
95   return idx;
96 }
97
98 /* Converts F to its string representation (for instance, "F8.2") and
99    returns a pointer to a static buffer containing that string. */
100 char *
101 fmt_to_string (const struct fmt_spec *f)
102 {
103   static char buf[32];
104
105   if (formats[f->type].n_args >= 2)
106     sprintf (buf, "%s%d.%d", formats[f->type].name, f->w, f->d);
107   else
108     sprintf (buf, "%s%d", formats[f->type].name, f->w);
109   return buf;
110 }
111
112 /* Checks whether SPEC is valid as an input format and returns
113    nonzero if so.  Otherwise, emits an error message and returns
114    zero. */
115 int
116 check_input_specifier (const struct fmt_spec *spec)
117 {
118   struct fmt_desc *f;
119   char *str;
120
121   f = &formats[spec->type];
122   str = fmt_to_string (spec);
123   if (spec->type == FMT_X)
124     return 1;
125   if (f->cat & FCAT_OUTPUT_ONLY)
126     {
127       msg (SE, _("Format %s may not be used as an input format."), f->name);
128       return 0;
129     }
130   if (spec->w < f->Imin_w || spec->w > f->Imax_w)
131     {
132       msg (SE, _("Input format %s specifies a bad width %d.  "
133                  "Format %s requires a width between %d and %d."),
134            str, spec->w, f->name, f->Imin_w, f->Imax_w);
135       return 0;
136     }
137   if ((f->cat & FCAT_EVEN_WIDTH) && spec->w % 2)
138     {
139       msg (SE, _("Input format %s specifies an odd width %d, but "
140                  "format %s requires an even width between %d and "
141                  "%d."), str, spec->w, f->name, f->Imin_w, f->Imax_w);
142       return 0;
143     }
144   if (f->n_args > 1 && (spec->d < 0 || spec->d > 16))
145     {
146       msg (SE, _("Input format %s specifies a bad number of "
147                  "implied decimal places %d.  Input format %s allows "
148                  "up to 16 implied decimal places."), str, spec->d, f->name);
149       return 0;
150     }
151   return 1;
152 }
153
154 /* Checks whether SPEC is valid as an output format and returns
155    nonzero if so.  Otherwise, emits an error message and returns
156    zero. */
157 int
158 check_output_specifier (const struct fmt_spec *spec)
159 {
160   struct fmt_desc *f;
161   char *str;
162
163   f = &formats[spec->type];
164   str = fmt_to_string (spec);
165   if (spec->type == FMT_X)
166     return 1;
167   if (spec->w < f->Omin_w || spec->w > f->Omax_w)
168     {
169       msg (SE, _("Output format %s specifies a bad width %d.  "
170                  "Format %s requires a width between %d and %d."),
171            str, spec->w, f->name, f->Omin_w, f->Omax_w);
172       return 0;
173     }
174   if (spec->d > 1
175       && (spec->type == FMT_F || spec->type == FMT_COMMA
176           || spec->type == FMT_DOLLAR)
177       && spec->w < f->Omin_w + 1 + spec->d)
178     {
179       msg (SE, _("Output format %s requires minimum width %d to allow "
180                  "%d decimal places.  Try %s%d.%d instead of %s."),
181            f->name, f->Omin_w + 1 + spec->d, spec->d, f->name,
182            f->Omin_w + 1 + spec->d, spec->d, str);
183       return 0;
184     }
185   if ((f->cat & FCAT_EVEN_WIDTH) && spec->w % 2)
186     {
187       msg (SE, _("Output format %s specifies an odd width %d, but "
188                  "output format %s requires an even width between %d and "
189                  "%d."), str, spec->w, f->name, f->Omin_w, f->Omax_w);
190       return 0;
191     }
192   if (f->n_args > 1 && (spec->d < 0 || spec->d > 16))
193     {
194       msg (SE, _("Output format %s specifies a bad number of "
195                  "implied decimal places %d.  Output format %s allows "
196                  "a number of implied decimal places between 1 "
197                  "and 16."), str, spec->d, f->name);
198       return 0;
199     }
200   return 1;
201 }
202
203 /* If a string variable has width W, you can't display it with a
204    format specifier with a required width MIN_LEN>W. */
205 int
206 check_string_specifier (const struct fmt_spec *f, int min_len)
207 {
208   if ((f->type == FMT_A && min_len > f->w)
209       || (f->type == FMT_AHEX && min_len * 2 > f->w))
210     {
211       msg (SE, _("Can't display a string variable of width %d with "
212                  "format specifier %s."), min_len, fmt_to_string (f));
213       return 0;
214     }
215   return 1;
216 }
217
218 /* Converts input format specifier INPUT into output format
219    specifier OUTPUT. */
220 void
221 convert_fmt_ItoO (const struct fmt_spec *input, struct fmt_spec *output)
222 {
223   output->type = formats[input->type].output;
224   output->w = input->w;
225   if (output->w > formats[output->type].Omax_w)
226     output->w = formats[output->type].Omax_w;
227   output->d = input->d;
228
229   switch (input->type)
230     {
231     case FMT_F:
232     case FMT_N:
233       if (output->d > 1 && output->w < 2 + output->d)
234         output->w = 2 + output->d;
235       break;
236     case FMT_E:
237       output->w = max (max (input->w, input->d+7), 10);
238       output->d = max (input->d, 3);
239       break;
240     case FMT_COMMA:
241     case FMT_DOT:
242       /* nothing is necessary */
243       break;
244     case FMT_DOLLAR:
245     case FMT_PCT:
246       if (output->w < 2)
247         output->w = 2;
248       break;
249     case FMT_PIBHEX:
250       {
251         static const int map[] = {4, 6, 9, 11, 14, 16, 18, 21};
252         assert (input->w % 2 == 0 && input->w >= 2 && input->w <= 16);
253         output->w = map[input->w / 2 - 1];
254         break;
255       }
256     case FMT_RBHEX:
257       output->w = 8, output->d = 2;     /* FIXME */
258       break;
259     case FMT_IB:
260     case FMT_PIB:
261     case FMT_P:
262     case FMT_PK:
263     case FMT_RB:
264       if (input->d < 1)
265         output->w = 8, output->d = 2;
266       else
267         output->w = 9 + input->d;
268       break;
269     case FMT_CCA:
270     case FMT_CCB:
271     case FMT_CCC:
272     case FMT_CCD:
273     case FMT_CCE:
274       assert (0);
275     case FMT_Z:
276     case FMT_A:
277       /* nothing is necessary */
278       break;
279     case FMT_AHEX:
280       output->w = input->w / 2;
281       break;
282     case FMT_DATE:
283     case FMT_EDATE:
284     case FMT_SDATE:
285     case FMT_ADATE:
286     case FMT_JDATE:
287       /* nothing is necessary */
288       break;
289     case FMT_QYR:
290       if (output->w < 6)
291         output->w = 6;
292       break;
293     case FMT_MOYR:
294       /* nothing is necessary */
295       break;
296     case FMT_WKYR:
297       if (output->w < 8)
298         output->w = 8;
299       break;
300     case FMT_TIME:
301     case FMT_DTIME:
302     case FMT_DATETIME:
303     case FMT_WKDAY:
304     case FMT_MONTH:
305       /* nothing is necessary */
306       break;
307     default:
308       assert (0);
309     }
310 }
311
312 /* Parses a format specifier from the token stream and returns
313    nonzero only if successful.  Emits an error message on
314    failure.  Allows X and T format specifiers only if ALLOW_XT is
315    nonzero.  The caller should call check_input_specifier() or
316    check_output_specifier() on the parsed format as
317    necessary.  */
318 int
319 parse_format_specifier (struct fmt_spec *input, int allow_xt)
320 {
321   struct fmt_spec spec;
322   struct fmt_desc *f;
323   const char *cp;
324   char *cp2;
325   int type, w, d;
326
327   if (token != T_ID)
328     {
329       msg (SE, _("Format specifier expected."));
330       return 0;
331     }
332   type = parse_format_specifier_name (&cp, allow_xt);
333   if (type == -1)
334     return 0;
335   f = &formats[type];
336
337   w = strtol (cp, &cp2, 10);
338   if (cp2 == cp && type != FMT_X)
339     {
340       msg (SE, _("Data format %s does not specify a width."),
341            ds_c_str (&tokstr));
342       return 0;
343     }
344
345   cp = cp2;
346   if (f->n_args > 1 && *cp == '.')
347     {
348       cp++;
349       d = strtol (cp, &cp2, 10);
350       cp = cp2;
351     }
352   else
353     d = 0;
354
355   if (*cp)
356     {
357       msg (SE, _("Data format %s is not valid."), ds_c_str (&tokstr));
358       return 0;
359     }
360   lex_get ();
361
362   spec.type = type;
363   spec.w = w;
364   spec.d = d;
365   *input = spec;
366
367   return 1;
368 }
369
370 /* Returns the width corresponding to the format specifier.  The
371    return value is the value of the `width' member of a `struct
372    variable' for such an input format. */
373 int
374 get_format_var_width (const struct fmt_spec *spec) 
375 {
376   if (spec->type == FMT_AHEX)
377     return spec->w * 2;
378   else if (spec->type == FMT_A)
379     return spec->w;
380   else
381     return 0;
382 }
383
384 /* Returns the PSPP format corresponding to the given SPSS
385    format. */
386 int
387 translate_fmt (int spss) 
388 {
389   int type;
390   
391   for (type = 0; type < FMT_NUMBER_OF_FORMATS; type++)
392     if (formats[type].spss == spss)
393       return type;
394   return -1;
395 }