Reworked settings so as to use one large struct instead of lots of static
[pspp-builds.git] / src / data / format.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2006 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "format.h"
20
21 #include <ctype.h>
22 #include <stdlib.h>
23
24 #include <data/identifier.h>
25 #include <data/settings.h>
26 #include <data/value.h>
27 #include <data/variable.h>
28 #include <libpspp/assertion.h>
29 #include <libpspp/compiler.h>
30 #include <libpspp/message.h>
31 #include <libpspp/misc.h>
32 #include <libpspp/str.h>
33
34 #include "minmax.h"
35 #include "xalloc.h"
36
37 #include "gettext.h"
38 #define _(msgid) gettext (msgid)
39
40
41
42 bool is_fmt_type (enum fmt_type);
43
44 static int min_width (enum fmt_type, bool for_input);
45 static int max_width (enum fmt_type);
46 static bool valid_width (enum fmt_type, int width, bool for_input);
47 static int max_decimals (enum fmt_type, int width, bool for_input);
48
49 static int max_digits_for_bytes (int bytes);
50
51 void fmt_number_style_init (struct fmt_number_style *style);
52
53
54 /* Initialize the format module. */
55 struct fmt_number_style *
56 fmt_create (void)
57 {
58   struct fmt_number_style *styles =
59     xcalloc (FMT_NUMBER_OF_FORMATS, sizeof (*styles));
60
61   int t;
62   for (t = 0 ; t < FMT_NUMBER_OF_FORMATS ; ++t )
63     fmt_number_style_init (&styles[t]);
64
65   fmt_set_decimal (styles, '.');
66
67   return styles;
68 }
69
70
71 /* Deinitialize the format module. */
72 void
73 fmt_done (struct fmt_number_style *styles)
74 {
75   int t;
76   for (t = 0 ; t < FMT_NUMBER_OF_FORMATS ; ++t )
77     fmt_number_style_destroy (&styles[t]);
78
79   free (styles);
80 }
81
82 /* Returns an input format specification with type TYPE, width W,
83    and D decimals. */
84 struct fmt_spec
85 fmt_for_input (enum fmt_type type, int w, int d)
86 {
87   struct fmt_spec f;
88   f.type = type;
89   f.w = w;
90   f.d = d;
91   assert (fmt_check_input (&f));
92   return f;
93 }
94
95 /* Returns an output format specification with type TYPE, width
96    W, and D decimals. */
97 struct fmt_spec
98 fmt_for_output (enum fmt_type type, int w, int d)
99 {
100   struct fmt_spec f;
101   f.type = type;
102   f.w = w;
103   f.d = d;
104   assert (fmt_check_output (&f));
105   return f;
106 }
107
108 /* Returns the output format specifier corresponding to input
109    format specifier INPUT. */
110 struct fmt_spec
111 fmt_for_output_from_input (const struct fmt_spec *input)
112 {
113   struct fmt_spec output;
114
115   assert (fmt_check_input (input));
116
117   output.type = fmt_input_to_output (input->type);
118   output.w = input->w;
119   if (output.w > fmt_max_output_width (output.type))
120     output.w = fmt_max_output_width (output.type);
121   else if (output.w < fmt_min_output_width (output.type))
122     output.w = fmt_min_output_width (output.type);
123   output.d = input->d;
124
125   switch (input->type)
126     {
127     case FMT_Z:
128       output.w++;
129       if (output.d > 0)
130         output.w++;
131       break;
132
133     case FMT_F:
134     case FMT_COMMA:
135     case FMT_DOT:
136     case FMT_DOLLAR:
137     case FMT_PCT:
138       {
139         const struct fmt_number_style *style =
140           settings_get_style (input->type);
141
142         output.w += fmt_affix_width (style);
143         if (style->grouping != 0 && input->w - input->d >= 3)
144           output.w += (input->w - input->d - 1) / 3;
145         if (output.d > 0)
146           output.w++;
147       }
148       break;
149
150     case FMT_N:
151       if (output.d > 0)
152         output.w++;
153       break;
154
155     case FMT_E:
156       output.d = MAX (input->d, 3);
157       output.w = MAX (input->w, output.d + 7);
158       break;
159
160     case FMT_PIBHEX:
161       output.w = max_digits_for_bytes (input->w / 2) + 1;
162       break;
163
164     case FMT_RB:
165     case FMT_RBHEX:
166       output.w = 8;
167       output.d = 2;
168       break;
169
170     case FMT_P:
171     case FMT_PK:
172       output.w = 2 * input->w + (input->d > 0);
173       break;
174
175     case FMT_IB:
176     case FMT_PIB:
177       output.w = max_digits_for_bytes (input->w) + 1;
178       if (output.d > 0)
179         output.w++;
180       break;
181
182     case FMT_CCA:
183     case FMT_CCB:
184     case FMT_CCC:
185     case FMT_CCD:
186     case FMT_CCE:
187       NOT_REACHED ();
188
189     case FMT_A:
190       break;
191
192     case FMT_AHEX:
193       output.w = input->w / 2;
194       break;
195
196     case FMT_DATE:
197     case FMT_EDATE:
198     case FMT_SDATE:
199     case FMT_ADATE:
200     case FMT_JDATE:
201     case FMT_QYR:
202     case FMT_MOYR:
203     case FMT_WKYR:
204     case FMT_TIME:
205     case FMT_DTIME:
206     case FMT_DATETIME:
207     case FMT_WKDAY:
208     case FMT_MONTH:
209       break;
210
211     default:
212       NOT_REACHED ();
213     }
214
215   if (output.w > fmt_max_output_width (output.type))
216     output.w = fmt_max_output_width (output.type);
217
218   assert (fmt_check_output (&output));
219   return output;
220 }
221
222 /* Returns the default format for the given WIDTH: F8.2 format
223    for a numeric value, A format for a string value. */
224 struct fmt_spec
225 fmt_default_for_width (int width)
226 {
227   return (width == 0
228           ? fmt_for_output (FMT_F, 8, 2)
229           : fmt_for_output (FMT_A, width, 0));
230 }
231
232 /* Checks whether SPEC is valid as an input format (if FOR_INPUT)
233    or an output format (otherwise) and returns nonzero if so.
234    Otherwise, emits an error message and returns zero. */
235 bool
236 fmt_check (const struct fmt_spec *spec, bool for_input)
237 {
238   const char *io_fmt = for_input ? _("Input format") : _("Output format");
239   char str[FMT_STRING_LEN_MAX + 1];
240   int min_w, max_w, max_d;
241
242   assert (is_fmt_type (spec->type));
243   fmt_to_string (spec, str);
244
245   if (for_input && !fmt_usable_for_input (spec->type))
246     {
247       msg (SE, _("Format %s may not be used for input."), str);
248       return false;
249     }
250
251   if (spec->w % fmt_step_width (spec->type))
252     {
253       assert (fmt_step_width (spec->type) == 2);
254       msg (SE, _("%s specifies width %d, but %s requires an even width."),
255            str, spec->w, fmt_name (spec->type));
256       return false;
257     }
258
259   min_w = min_width (spec->type, for_input);
260   max_w = max_width (spec->type);
261   if (spec->w < min_w || spec->w > max_w)
262     {
263       msg (SE, _("%s %s specifies width %d, but "
264                  "%s requires a width between %d and %d."),
265            io_fmt, str, spec->w, fmt_name (spec->type), min_w, max_w);
266       return false;
267     }
268
269   max_d = max_decimals (spec->type, spec->w, for_input);
270   if (!fmt_takes_decimals (spec->type) && spec->d != 0)
271     {
272       msg (SE, ngettext ("%s %s specifies %d decimal place, but "
273                          "%s does not allow any decimals.",
274                          "%s %s specifies %d decimal places, but "
275                          "%s does not allow any decimals.",
276                          spec->d),
277            io_fmt, str, spec->d, fmt_name (spec->type));
278       return false;
279     }
280   else if (spec->d > max_d)
281     {
282       if (max_d > 0)
283         msg (SE, ngettext ("%s %s specifies %d decimal place, but "
284                            "the given width allows at most %d decimals.",
285                            "%s %s specifies %d decimal places, but "
286                            "the given width allows at most %d decimals.",
287                            spec->d),
288              io_fmt, str, spec->d, max_d);
289       else
290         msg (SE, ngettext ("%s %s specifies %d decimal place, but "
291                            "the given width does not allow for any decimals.",
292                            "%s %s specifies %d decimal places, but "
293                            "the given width does not allow for any decimals.",
294                            spec->d),
295              io_fmt, str, spec->d);
296       return false;
297     }
298
299   return true;
300 }
301
302 /* Checks whether SPEC is valid as an input format and returns
303    nonzero if so.  Otherwise, emits an error message and returns
304    zero. */
305 bool
306 fmt_check_input (const struct fmt_spec *spec)
307 {
308   return fmt_check (spec, true);
309 }
310
311 /* Checks whether SPEC is valid as an output format and returns
312    true if so.  Otherwise, emits an error message and returns false. */
313 bool
314 fmt_check_output (const struct fmt_spec *spec)
315 {
316   return fmt_check (spec, false);
317 }
318
319 /* Checks that FORMAT is appropriate for a variable of the given
320    VAR_TYPE and returns true if so.  Otherwise returns false and
321    emits an error message. */
322 bool
323 fmt_check_type_compat (const struct fmt_spec *format, enum val_type var_type)
324 {
325   assert (val_type_is_valid (var_type));
326   if ((var_type == VAL_STRING) != (fmt_is_string (format->type) != 0))
327     {
328       char str[FMT_STRING_LEN_MAX + 1];
329       msg (SE, _("%s variables are not compatible with %s format %s."),
330            var_type == VAL_STRING ? _("String") : _("Numeric"),
331            var_type == VAL_STRING ? _("numeric") : _("string"),
332            fmt_to_string (format, str));
333       return false;
334     }
335   return true;
336 }
337
338 /* Checks that FORMAT is appropriate for a variable of the given
339    WIDTH and returns true if so.  Otherwise returns false and
340    emits an error message. */
341 bool
342 fmt_check_width_compat (const struct fmt_spec *format, int width)
343 {
344   if (!fmt_check_type_compat (format, val_type_from_width (width)))
345     return false;
346   if (fmt_var_width (format) != width)
347     {
348       char str[FMT_STRING_LEN_MAX + 1];
349       msg (SE, _("String variable with width %d is not compatible with "
350                  "format %s."),
351            width, fmt_to_string (format, str));
352       return false;
353     }
354   return true;
355 }
356
357 /* Returns the width corresponding to FORMAT.  The return value
358    is the width of the `union value's required by FORMAT. */
359 int
360 fmt_var_width (const struct fmt_spec *format)
361 {
362   return (format->type == FMT_AHEX ? format->w / 2
363           : format->type == FMT_A ? format->w
364           : 0);
365 }
366
367 /* Converts F to its string representation (for instance, "F8.2")
368    in BUFFER.  Returns BUFFER.
369
370    If F has decimals, they are included in the output string,
371    even if F's format type does not allow decimals, to allow
372    accurately presenting incorrect formats to the user. */
373 char *
374 fmt_to_string (const struct fmt_spec *f, char buffer[FMT_STRING_LEN_MAX + 1])
375 {
376   if (fmt_takes_decimals (f->type) || f->d > 0)
377     snprintf (buffer, FMT_STRING_LEN_MAX + 1,
378               "%s%d.%d", fmt_name (f->type), f->w, f->d);
379   else
380     snprintf (buffer, FMT_STRING_LEN_MAX + 1,
381               "%s%d", fmt_name (f->type), f->w);
382   return buffer;
383 }
384
385 /* Returns true if A and B are identical formats,
386    false otherwise. */
387 bool
388 fmt_equal (const struct fmt_spec *a, const struct fmt_spec *b)
389 {
390   return a->type == b->type && a->w == b->w && a->d == b->d;
391 }
392
393 /* Adjusts FMT to be valid for a value of the given WIDTH. */
394 void
395 fmt_resize (struct fmt_spec *fmt, int width)
396 {
397   if ((width > 0) != fmt_is_string (fmt->type))
398     {
399       /* Changed from numeric to string or vice versa.  Set to
400          default format for new width. */
401       *fmt = fmt_default_for_width (width);
402     }
403   else if (width > 0)
404     {
405       /* Changed width of string.  Preserve format type, adjust
406          width. */
407       fmt->w = fmt->type == FMT_AHEX ? width * 2 : width;
408     }
409   else
410     {
411       /* Still numeric. */
412     }
413 }
414 \f
415 /* Describes a display format. */
416 struct fmt_desc
417   {
418     char name[9];
419     int min_input_width, min_output_width;
420     int io;
421     enum fmt_category category;
422   };
423
424 static const struct fmt_desc *get_fmt_desc (enum fmt_type type);
425
426 /* Returns the name of the given format TYPE. */
427 const char *
428 fmt_name (enum fmt_type type)
429 {
430   return get_fmt_desc (type)->name;
431 }
432
433 /* Tries to parse NAME as a format type.
434    If successful, stores the type in *TYPE and returns true.
435    On failure, returns false. */
436 bool
437 fmt_from_name (const char *name, enum fmt_type *type)
438 {
439   int i;
440
441   for (i = 0; i < FMT_NUMBER_OF_FORMATS; i++)
442     if (!strcasecmp (name, get_fmt_desc (i)->name))
443       {
444         *type = i;
445         return true;
446       }
447   return false;
448 }
449
450 /* Returns true if TYPE accepts decimal places,
451    false otherwise. */
452 bool
453 fmt_takes_decimals (enum fmt_type type)
454 {
455   return fmt_max_output_decimals (type, fmt_max_output_width (type)) > 0;
456 }
457
458 /* Returns the minimum acceptable width for an input field
459    formatted with the given TYPE. */
460 int
461 fmt_min_input_width (enum fmt_type type)
462 {
463   return get_fmt_desc (type)->min_input_width;
464 }
465
466 /* Returns the maximum acceptable width for an input field
467    formatted with the given TYPE. */
468 int
469 fmt_max_input_width (enum fmt_type type)
470 {
471   return max_width (type);
472 }
473
474 /* Returns the maximum number of decimal places allowed in an
475    input field of the given TYPE and WIDTH. */
476 int
477 fmt_max_input_decimals (enum fmt_type type, int width)
478 {
479   assert (valid_width (type, width, true));
480   return max_decimals (type, width, true);
481 }
482
483 /* Returns the minimum acceptable width for an output field
484    formatted with the given TYPE. */
485 int
486 fmt_min_output_width (enum fmt_type type)
487 {
488   return get_fmt_desc (type)->min_output_width;
489 }
490
491 /* Returns the maximum acceptable width for an output field
492    formatted with the given TYPE. */
493 int
494 fmt_max_output_width (enum fmt_type type)
495 {
496   return max_width (type);
497 }
498
499 /* Returns the maximum number of decimal places allowed in an
500    output field of the given TYPE and WIDTH. */
501 int
502 fmt_max_output_decimals (enum fmt_type type, int width)
503 {
504   assert (valid_width (type, width, false));
505   return max_decimals (type, width, false);
506 }
507
508 /* Returns the width step for a field formatted with the given
509    TYPE.  Field width must be a multiple of the width step. */
510 int
511 fmt_step_width (enum fmt_type type)
512 {
513   return (fmt_get_category (type) == FMT_CAT_HEXADECIMAL || type == FMT_AHEX
514           ? 2 : 1);
515 }
516
517 /* Returns true if TYPE is used for string fields,
518    false if it is used for numeric fields. */
519 bool
520 fmt_is_string (enum fmt_type type)
521 {
522   return fmt_get_category (type) == FMT_CAT_STRING;
523 }
524
525 /* Returns true if TYPE is used for numeric fields,
526    false if it is used for string fields. */
527 bool
528 fmt_is_numeric (enum fmt_type type)
529 {
530   return !fmt_is_string (type);
531 }
532
533 /* Returns the format TYPE's category.
534    Each format type is in exactly one category,
535    and each category's value is bitwise disjoint from every other
536    category.  Thus, the return value may be tested for equality
537    or compared bitwise against a mask of FMT_CAT_* values. */
538 enum fmt_category
539 fmt_get_category (enum fmt_type type)
540 {
541   return get_fmt_desc (type)->category;
542 }
543
544 /* Returns the output format selected by default when TYPE is
545    used as an input format. */
546 enum fmt_type
547 fmt_input_to_output (enum fmt_type type)
548 {
549   switch (fmt_get_category (type))
550     {
551     case FMT_CAT_STRING:
552       return FMT_A;
553
554     case FMT_CAT_LEGACY:
555     case FMT_CAT_BINARY:
556     case FMT_CAT_HEXADECIMAL:
557       return FMT_F;
558
559     default:
560       return type;
561     }
562 }
563
564 /* Returns the SPSS format type corresponding to the given PSPP
565    format type. */
566 int
567 fmt_to_io (enum fmt_type type)
568 {
569   return get_fmt_desc (type)->io;
570 }
571
572 /* Determines the PSPP format corresponding to the given SPSS
573    format type.  If successful, sets *FMT_TYPE to the PSPP format
574    and returns true.  On failure, return false. */
575 bool
576 fmt_from_io (int io, enum fmt_type *fmt_type)
577 {
578   enum fmt_type type;
579
580   for (type = 0; type < FMT_NUMBER_OF_FORMATS; type++)
581     if (get_fmt_desc (type)->io == io)
582       {
583         *fmt_type = type;
584         return true;
585       }
586   return false;
587 }
588
589 /* Returns true if TYPE may be used as an input format,
590    false otherwise. */
591 bool
592 fmt_usable_for_input (enum fmt_type type)
593 {
594   assert (is_fmt_type (type));
595   return fmt_get_category (type) != FMT_CAT_CUSTOM;
596 }
597
598 /* For time and date formats, returns a template used for input
599    and output. */
600 const char *
601 fmt_date_template (enum fmt_type type)
602 {
603   switch (type)
604     {
605     case FMT_DATE:
606       return "dd-mmm-yy";
607     case FMT_ADATE:
608       return "mm/dd/yy";
609     case FMT_EDATE:
610       return "dd.mm.yy";
611     case FMT_JDATE:
612       return "yyddd";
613     case FMT_SDATE:
614       return "yy/mm/dd";
615     case FMT_QYR:
616       return "q Q yy";
617     case FMT_MOYR:
618       return "mmmXyy";
619     case FMT_WKYR:
620       return "ww WK yy";
621     case FMT_DATETIME:
622       return "dd-mmm-yyyy HH:MM";
623     case FMT_TIME:
624       return "H:MM";
625     case FMT_DTIME:
626       return "D HH:MM";
627     default:
628       NOT_REACHED ();
629     }
630 }
631
632 \f
633 /* Returns true if TYPE is a valid format type,
634    false otherwise. */
635 bool
636 is_fmt_type (enum fmt_type type)
637 {
638   return type < FMT_NUMBER_OF_FORMATS;
639 }
640
641 /* Returns the minimum width of the given format TYPE,
642    for input if FOR_INPUT is true,
643    for output otherwise. */
644 static int
645 min_width (enum fmt_type type, bool for_input)
646 {
647   return for_input ? fmt_min_input_width (type) : fmt_min_output_width (type);
648 }
649
650 /* Returns the maximum width of the given format TYPE,
651    which is invariant between input and output. */
652 static int
653 max_width (enum fmt_type type)
654 {
655   assert (is_fmt_type (type));
656   switch (type)
657     {
658     case FMT_P:
659     case FMT_PK:
660     case FMT_PIBHEX:
661     case FMT_RBHEX:
662       return 16;
663
664     case FMT_IB:
665     case FMT_PIB:
666     case FMT_RB:
667       return 8;
668
669     case FMT_A:
670       return MAX_STRING;
671
672     case FMT_AHEX:
673       return 2 * MAX_STRING;
674
675     default:
676       return 40;
677     }
678 }
679
680 /* Returns true if WIDTH is a valid width for the given format
681    TYPE,
682    for input if FOR_INPUT is true,
683    for output otherwise. */
684 static bool
685 valid_width (enum fmt_type type, int width, bool for_input)
686 {
687   return (width >= min_width (type, for_input)
688           && width <= max_width (type));
689 }
690
691 /* Returns the maximum number of decimal places allowed for the
692    given format TYPE with a width of WIDTH places,
693    for input if FOR_INPUT is true,
694    for output otherwise. */
695 static int
696 max_decimals (enum fmt_type type, int width, bool for_input)
697 {
698   int max_d;
699
700   switch (type)
701     {
702     case FMT_F:
703     case FMT_COMMA:
704     case FMT_DOT:
705       max_d = for_input ? width : width - 1;
706       break;
707
708     case FMT_DOLLAR:
709     case FMT_PCT:
710       max_d = for_input ? width : width - 2;
711       break;
712
713     case FMT_E:
714       max_d = for_input ? width : width - 7;
715       break;
716
717     case FMT_CCA:
718     case FMT_CCB:
719     case FMT_CCC:
720     case FMT_CCD:
721     case FMT_CCE:
722       assert (!for_input);
723       max_d = width - 1;
724       break;
725
726     case FMT_N:
727     case FMT_Z:
728       max_d = width;
729       break;
730
731     case FMT_P:
732       max_d = width * 2 - 1;
733       break;
734
735     case FMT_PK:
736       max_d = width * 2;
737       break;
738
739     case FMT_IB:
740     case FMT_PIB:
741       max_d = max_digits_for_bytes (width);
742       break;
743
744     case FMT_PIBHEX:
745       max_d = 0;
746       break;
747
748     case FMT_RB:
749     case FMT_RBHEX:
750       max_d = 16;
751       break;
752
753     case FMT_DATE:
754     case FMT_ADATE:
755     case FMT_EDATE:
756     case FMT_JDATE:
757     case FMT_SDATE:
758     case FMT_QYR:
759     case FMT_MOYR:
760     case FMT_WKYR:
761       max_d = 0;
762       break;
763
764     case FMT_DATETIME:
765       max_d = width - 21;
766       break;
767
768     case FMT_TIME:
769       max_d = width - 9;
770       break;
771
772     case FMT_DTIME:
773       max_d = width - 12;
774       break;
775
776     case FMT_WKDAY:
777     case FMT_MONTH:
778     case FMT_A:
779     case FMT_AHEX:
780       max_d = 0;
781       break;
782
783     default:
784       NOT_REACHED ();
785     }
786
787   if (max_d < 0)
788     max_d = 0;
789   else if (max_d > 16)
790     max_d = 16;
791   return max_d;
792 }
793
794 /* Returns the maximum number of decimal digits in an unsigned
795    binary number that is BYTES bytes long. */
796 static int
797 max_digits_for_bytes (int bytes)
798 {
799   int map[8] = {3, 5, 8, 10, 13, 15, 17, 20};
800   assert (bytes > 0 && bytes <= sizeof map / sizeof *map);
801   return map[bytes - 1];
802 }
803 \f
804
805
806 void
807 fmt_number_style_init (struct fmt_number_style *style)
808 {
809   style->neg_prefix = ss_empty ();
810   style->prefix = ss_empty ();
811   style->suffix = ss_empty ();
812   style->neg_suffix = ss_empty ();
813   style->decimal = '.';
814   style->grouping = 0;
815 }
816
817
818 /* Destroys a struct fmt_number_style. */
819 void
820 fmt_number_style_destroy (struct fmt_number_style *style)
821 {
822   if (style != NULL)
823     {
824       ss_dealloc (&style->neg_prefix);
825       ss_dealloc (&style->prefix);
826       ss_dealloc (&style->suffix);
827       ss_dealloc (&style->neg_suffix);
828     }
829 }
830
831 /* Returns the number formatting style associated with the given
832    format TYPE. */
833 const struct fmt_number_style *
834 fmt_get_style (const struct fmt_number_style *styles, enum fmt_type type)
835 {
836   assert (is_fmt_type (type));
837   return &styles[type];
838 }
839
840
841 /* Checks that style is STYLE sane */
842 void
843 fmt_check_style (const struct fmt_number_style *style)
844 {
845   assert (ss_length (style->neg_prefix) <= FMT_STYLE_AFFIX_MAX);
846   assert (ss_length (style->prefix) <= FMT_STYLE_AFFIX_MAX);
847   assert (ss_length (style->suffix) <= FMT_STYLE_AFFIX_MAX);
848   assert (ss_length (style->neg_suffix) <= FMT_STYLE_AFFIX_MAX);
849   assert (style->decimal == '.' || style->decimal == ',');
850   assert (style->grouping == '.' || style->grouping == ','
851           || style->grouping == 0);
852   assert (style->grouping != style->decimal);
853 }
854
855
856 /* Returns the total width of the standard prefix and suffix for
857    STYLE. */
858 int
859 fmt_affix_width (const struct fmt_number_style *style)
860 {
861   return ss_length (style->prefix) + ss_length (style->suffix);
862 }
863
864 /* Returns the total width of the negative prefix and suffix for
865    STYLE. */
866 int
867 fmt_neg_affix_width (const struct fmt_number_style *style)
868 {
869   return ss_length (style->neg_prefix) + ss_length (style->neg_suffix);
870 }
871
872
873 /* Sets the number style for TYPE to have the given standard
874    PREFIX and SUFFIX, "-" as prefix suffix, an empty negative
875    suffix, DECIMAL as the decimal point character, and GROUPING
876    as the grouping character. */
877 static void
878 set_style (struct fmt_number_style *styles, enum fmt_type type,
879            const char *prefix, const char *suffix,
880            char decimal, char grouping)
881 {
882   struct fmt_number_style *style;
883
884   assert (is_fmt_type (type));
885
886   style = &styles[type] ;
887
888   fmt_number_style_destroy (style);
889
890   ss_alloc_substring (&style->neg_prefix, ss_cstr ("-"));
891   ss_alloc_substring (&style->prefix, ss_cstr (prefix));
892   ss_alloc_substring (&style->suffix, ss_cstr (suffix));
893   style->decimal = decimal;
894   style->grouping = grouping;
895 }
896
897 /* Sets the decimal point character to DECIMAL. */
898 void
899 fmt_set_decimal (struct fmt_number_style *styles, char decimal)
900 {
901   int grouping = decimal == '.' ? ',' : '.';
902   assert (decimal == '.' || decimal == ',');
903
904   set_style (styles, FMT_F, "", "", decimal, 0);
905   set_style (styles, FMT_E, "", "", decimal, 0);
906   set_style (styles, FMT_COMMA, "", "", decimal, grouping);
907   set_style (styles, FMT_DOT, "", "", grouping, decimal);
908   set_style (styles, FMT_DOLLAR, "$", "", decimal, grouping);
909   set_style (styles, FMT_PCT, "", "%", decimal, 0);
910 }
911 \f
912 /* Returns the struct fmt_desc for the given format TYPE. */
913 static const struct fmt_desc *
914 get_fmt_desc (enum fmt_type type)
915 {
916   static const struct fmt_desc formats[FMT_NUMBER_OF_FORMATS] =
917     {
918 #define FMT(NAME, METHOD, IMIN, OMIN, IO, CATEGORY) \
919         {#NAME, IMIN, OMIN, IO, CATEGORY},
920 #include "format.def"
921     };
922
923   assert (is_fmt_type (type));
924   return &formats[type];
925 }