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