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