Fix prototype of __printf_fp.
[kopensolaris-gnu/glibc.git] / stdlib / strfmon.c
1 /* Formatting a monetary value according to the current locale.
2    Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@cygnus.com>
5    and Jochen Hein <Jochen.Hein@informatik.TU-Clausthal.de>, 1996.
6
7    The GNU C Library is free software; you can redistribute it and/or
8    modify it under the terms of the GNU Library General Public License as
9    published by the Free Software Foundation; either version 2 of the
10    License, or (at your option) any later version.
11
12    The GNU C Library is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Library General Public License for more details.
16
17    You should have received a copy of the GNU Library General Public
18    License along with the GNU C Library; see the file COPYING.LIB.  If not,
19    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20    Boston, MA 02111-1307, USA.  */
21
22 #include <ctype.h>
23 #include <errno.h>
24 #include <langinfo.h>
25 #include <monetary.h>
26 #ifdef USE_IN_LIBIO
27 # include "../libio/libioP.h"
28 # include "../libio/strfile.h"
29 #endif
30 #include <printf.h>
31 #include <stdarg.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include "../locale/localeinfo.h"
35
36
37 #define out_char(Ch)                                                          \
38   do {                                                                        \
39     if (dest >= s + maxsize - 1)                                              \
40       {                                                                       \
41         __set_errno (E2BIG);                                                  \
42         va_end (ap);                                                          \
43         return -1;                                                            \
44       }                                                                       \
45     *dest++ = (Ch);                                                           \
46   } while (0)
47
48 #define out_string(String)                                                    \
49   do {                                                                        \
50     const char *_s = (String);                                                \
51     while (*_s)                                                               \
52       out_char (*_s++);                                                       \
53   } while (0)
54
55 #define out_nstring(String, N)                                                \
56   do {                                                                        \
57     int _n = (N);                                                             \
58     const char *_s = (String);                                                \
59     while (_n-- > 0)                                                          \
60       out_char (*_s++);                                                       \
61   } while (0)
62
63 #define to_digit(Ch) ((Ch) - '0')
64
65
66 /* We use this code also for the extended locale handling where the
67    function gets as an additional argument the locale which has to be
68    used.  To access the values we have to redefine the _NL_CURRENT
69    macro.  */
70 #ifdef USE_IN_EXTENDED_LOCALE_MODEL
71 # undef _NL_CURRENT
72 # define _NL_CURRENT(category, item) \
73   (current->values[_NL_ITEM_INDEX (item)].string)
74 #endif
75
76 extern int __printf_fp (FILE *, const struct printf_info *,
77                         const void *const *);
78 /* This function determines the number of digit groups in the output.
79    The definition is in printf_fp.c.  */
80 extern unsigned int __guess_grouping (unsigned int intdig_max,
81                                       const char *grouping, wchar_t sepchar);
82
83
84 /* We have to overcome some problems with this implementation.  On the
85    one hand the strfmon() function is specified in XPG4 and of course
86    it has to follow this.  But on the other hand POSIX.2 specifies
87    some information in the LC_MONETARY category which should be used,
88    too.  Some of the information contradicts the information which can
89    be specified in format string.  */
90 #ifndef USE_IN_EXTENDED_LOCALE_MODEL
91 ssize_t
92 strfmon (char *s, size_t maxsize, const char *format, ...)
93 #else
94 ssize_t
95 __strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
96 #endif
97 {
98 #ifdef USE_IN_EXTENDED_LOCALE_MODEL
99   struct locale_data *current = loc->__locales[LC_MONETARY];
100 #endif
101 #ifdef USE_IN_LIBIO
102   _IO_strfile f;
103 #else
104   FILE f;
105 #endif
106   struct printf_info info;
107   va_list ap;                   /* Scan through the varargs.  */
108   char *dest;                   /* Pointer so copy the output.  */
109   const char *fmt;              /* Pointer that walks through format.  */
110
111   va_start (ap, format);
112
113   dest = s;
114   fmt = format;
115
116   /* Loop through the format-string.  */
117   while (*fmt != '\0')
118     {
119       /* The floating-point value to output.  */
120       union
121       {
122         double dbl;
123         __long_double_t ldbl;
124       }
125       fpnum;
126       int print_curr_symbol;
127       int left_prec;
128       int left_pad;
129       int right_prec;
130       int group;
131       char pad;
132       int is_long_double;
133       int p_sign_posn;
134       int n_sign_posn;
135       int sign_posn;
136       int other_sign_posn;
137       int left;
138       int is_negative;
139       int sep_by_space;
140       int other_sep_by_space;
141       int cs_precedes;
142       int other_cs_precedes;
143       const char *sign_string;
144       const char *other_sign_string;
145       int done;
146       const char *currency_symbol;
147       size_t currency_symbol_len;
148       int width;
149       char *startp;
150       const void *ptr;
151       char space_char;
152
153       /* Process all character which do not introduce a format
154          specification.  */
155       if (*fmt != '%')
156         {
157           out_char (*fmt++);
158           continue;
159         }
160
161       /* "%%" means a single '%' character.  */
162       if (fmt[1] == '%')
163         {
164           out_char (*++fmt);
165           ++fmt;
166           continue;
167         }
168
169       /* Defaults for formatting.  */
170       print_curr_symbol = 1;            /* Print the currency symbol.  */
171       left_prec = -1;                   /* No left precision specified.  */
172       right_prec = -1;                  /* No right precision specified.  */
173       group = 1;                        /* Print digits grouped.  */
174       pad = ' ';                        /* Fill character is <SP>.  */
175       is_long_double = 0;               /* Double argument by default.  */
176       p_sign_posn = -1;                 /* This indicates whether the */
177       n_sign_posn = -1;                 /* '(' flag is given.  */
178       width = -1;                       /* No width specified so far.  */
179       left = 0;                         /* Right justified by default.  */
180
181       /* Parse group characters.  */
182       while (1)
183         {
184           switch (*++fmt)
185             {
186             case '=':                   /* Set fill character.  */
187               pad = *++fmt;
188               if (pad == '\0')
189                 {
190                   /* Premature EOS.  */
191                   __set_errno (EINVAL);
192                   va_end (ap);
193                   return -1;
194                 }
195               continue;
196             case '^':                   /* Don't group digits.  */
197               group = 0;
198               continue;
199             case '+':                   /* Use +/- for sign of number.  */
200               if (n_sign_posn != -1)
201                 {
202                   __set_errno (EINVAL);
203                   va_end (ap);
204                   return -1;
205                 }
206               p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
207               n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
208               continue;
209             case '(':                   /* Use ( ) for negative sign.  */
210               if (n_sign_posn != -1)
211                 {
212                   __set_errno (EINVAL);
213                   va_end (ap);
214                   return -1;
215                 }
216               p_sign_posn = 0;
217               n_sign_posn = 0;
218               continue;
219             case '!':                   /* Don't print the currency symbol.  */
220               print_curr_symbol = 0;
221               continue;
222             case '-':                   /* Print left justified.  */
223               left = 1;
224               continue;
225             default:
226               /* Will stop the loop.  */;
227             }
228           break;
229         }
230
231       /* If not specified by the format string now find the values for
232          the format specification.  */
233       if (p_sign_posn == -1)
234         p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
235       if (n_sign_posn == -1)
236         n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
237
238       if (isdigit (*fmt))
239         {
240           /* Parse field width.  */
241           width = to_digit (*fmt);
242
243           while (isdigit (*++fmt))
244             {
245               width *= 10;
246               width += to_digit (*fmt);
247             }
248
249           /* If we don't have enough room for the demanded width we
250              can stop now and return an error.  */
251           if (dest + width >= s + maxsize)
252             {
253               __set_errno (E2BIG);
254               va_end (ap);
255               return -1;
256             }
257         }
258
259       /* Recognize left precision.  */
260       if (*fmt == '#')
261         {
262           if (!isdigit (*++fmt))
263             {
264               __set_errno (EINVAL);
265               va_end (ap);
266               return -1;
267             }
268           left_prec = to_digit (*fmt);
269
270           while (isdigit (*++fmt))
271             {
272               left_prec *= 10;
273               left_prec += to_digit (*fmt);
274             }
275         }
276
277       /* Recognize right precision.  */
278       if (*fmt == '.')
279         {
280           if (!isdigit (*++fmt))
281             {
282               __set_errno (EINVAL);
283               va_end (ap);
284               return -1;
285             }
286           right_prec = to_digit (*fmt);
287
288           while (isdigit (*++fmt))
289             {
290               right_prec *= 10;
291               right_prec += to_digit (*fmt);
292             }
293         }
294
295       /* Handle modifier.  This is an extension.  */
296       if (*fmt == 'L')
297         {
298           ++fmt;
299           is_long_double = 1;
300         }
301
302       /* Handle format specifier.  */
303       switch (*fmt++)
304         {
305         case 'i':               /* Use international currency symbol.  */
306           currency_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
307           currency_symbol_len = 3;
308           space_char = currency_symbol[3];
309           if (right_prec == -1)
310             {
311               if (*_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS) == CHAR_MAX)
312                 right_prec = 2;
313               else
314                 right_prec = *_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS);
315             }
316           break;
317         case 'n':               /* Use national currency symbol.  */
318           currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
319           currency_symbol_len = strlen (currency_symbol);
320           space_char = ' ';
321           if (right_prec == -1)
322             {
323               if (*_NL_CURRENT (LC_MONETARY, FRAC_DIGITS) == CHAR_MAX)
324                 right_prec = 2;
325               else
326                 right_prec = *_NL_CURRENT (LC_MONETARY, FRAC_DIGITS);
327             }
328           break;
329         default:                /* Any unrecognized format is an error.  */
330           __set_errno (EINVAL);
331           va_end (ap);
332           return -1;
333         }
334
335       /* If we have to print the digits grouped determine how many
336          extra characters this means.  */
337       if (group && left_prec != -1)
338         left_prec += __guess_grouping (left_prec,
339                                        _NL_CURRENT (LC_MONETARY, MON_GROUPING),
340                                        *_NL_CURRENT (LC_MONETARY,
341                                                      MON_THOUSANDS_SEP));
342
343       /* Now it's time to get the value.  */
344       if (is_long_double == 1)
345         {
346           fpnum.ldbl = va_arg (ap, long double);
347           is_negative = fpnum.ldbl < 0;
348           if (is_negative)
349             fpnum.ldbl = -fpnum.ldbl;
350         }
351       else
352         {
353           fpnum.dbl = va_arg (ap, double);
354           is_negative = fpnum.dbl < 0;
355           if (is_negative)
356             fpnum.dbl = -fpnum.dbl;
357         }
358
359       /* We now know the sign of the value and can determine the format.  */
360       if (is_negative)
361         {
362           sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
363           /* If the locale does not specify a character for the
364              negative sign we use a '-'.  */
365           if (*sign_string == '\0')
366             sign_string = (const char *) "-";
367           cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
368           sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
369           sign_posn = n_sign_posn;
370
371           other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
372           other_cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
373           other_sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
374           other_sign_posn = p_sign_posn;
375         }
376       else
377         {
378           sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
379           cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
380           sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
381           sign_posn = p_sign_posn;
382
383           other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
384           if (*other_sign_string == '\0')
385             other_sign_string = (const char *) "-";
386           other_cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
387           other_sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
388           other_sign_posn = n_sign_posn;
389         }
390
391       /* Set default values for unspecified information.  */
392       if (cs_precedes != 0)
393         cs_precedes = 1;
394       if (other_cs_precedes != 0)
395         other_cs_precedes = 1;
396       if (sep_by_space == CHAR_MAX)
397         sep_by_space = 0;
398       if (other_sep_by_space == CHAR_MAX)
399         other_sep_by_space = 0;
400       if (sign_posn == CHAR_MAX)
401         sign_posn = 1;
402       if (other_sign_posn == CHAR_MAX)
403         other_sign_posn = 1;
404
405       /* Check for degenerate cases */
406       if (sep_by_space == 2)
407         {
408           if (sign_posn == 0 ||
409               (sign_posn == 1 && !cs_precedes) ||
410               (sign_posn == 2 && cs_precedes))
411             /* sign and symbol are not adjacent, so no separator */
412             sep_by_space = 0;
413         }
414       if (other_sep_by_space == 2)
415         {
416           if (other_sign_posn == 0 ||
417               (other_sign_posn == 1 && !other_cs_precedes) ||
418               (other_sign_posn == 2 && other_cs_precedes))
419             /* sign and symbol are not adjacent, so no separator */
420             other_sep_by_space = 0;
421         }
422
423       /* Set the left precision and padding needed for alignment */
424       if (left_prec == -1)
425         {
426           left_prec = 0;
427           left_pad = 0;
428         }
429       else
430         {
431           /* Set left_pad to number of spaces needed to align positive
432              and negative formats */
433
434           int left_bytes = 0;
435           int other_left_bytes = 0;
436
437           /* Work out number of bytes for currency string and separator
438              preceding the value */
439           if (cs_precedes)
440             {
441               left_bytes += currency_symbol_len;
442               if (sep_by_space != 0)
443                 ++left_bytes;
444             }
445
446           if (other_cs_precedes)
447             {
448               other_left_bytes += currency_symbol_len;
449               if (other_sep_by_space != 0)
450                 ++other_left_bytes;
451             }
452
453           /* Work out number of bytes for the sign (or left parenthesis)
454              preceding the value */
455           if (sign_posn == 0 && is_negative)
456             ++left_bytes;
457           else if (sign_posn == 1)
458             left_bytes += strlen (sign_string);
459           else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
460             left_bytes += strlen (sign_string);
461
462           if (other_sign_posn == 0 && !is_negative)
463             ++other_left_bytes;
464           else if (other_sign_posn == 1)
465             other_left_bytes += strlen (other_sign_string);
466           else if (other_cs_precedes &&
467                    (other_sign_posn == 3 || other_sign_posn == 4))
468             other_left_bytes += strlen (other_sign_string);
469
470           /* Compare the number of bytes preceding the value for
471              each format, and set the padding accordingly */
472           if (other_left_bytes > left_bytes)
473             left_pad = other_left_bytes - left_bytes;
474           else
475             left_pad = 0;
476         }
477
478       /* Perhaps we'll someday make these things configurable so
479          better start using symbolic names now.  */
480 #define left_paren '('
481 #define right_paren ')'
482
483       startp = dest;            /* Remember start so we can compute length.  */
484
485       while (left_pad-- > 0)
486         out_char (' ');
487
488       if (sign_posn == 0 && is_negative)
489         out_char (left_paren);
490
491       if (cs_precedes)
492         {
493           if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
494               && sign_posn != 5)
495             {
496               out_string (sign_string);
497               if (sep_by_space == 2)
498                 out_char (' ');
499             }
500
501           if (print_curr_symbol)
502             {
503               out_string (currency_symbol);
504
505               if (sign_posn == 4)
506                 {
507                   if (sep_by_space == 2)
508                     out_char (space_char);
509                   out_string (sign_string);
510                   if (sep_by_space == 1)
511                     /* POSIX.2 and SUS are not clear on this case, but C99
512                        says a space follows the adjacent-symbol-and-sign */
513                     out_char (' ');
514                 }
515               else
516                 if (sep_by_space == 1)
517                   out_char (space_char);
518             }
519         }
520       else
521         if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
522             && sign_posn != 4 && sign_posn != 5)
523           out_string (sign_string);
524
525       /* Print the number.  */
526 #ifdef USE_IN_LIBIO
527       _IO_init ((_IO_FILE *) &f, 0);
528       _IO_JUMPS ((struct _IO_FILE_plus *) &f) = &_IO_str_jumps;
529       _IO_str_init_static ((_IO_strfile *) &f, dest, (s + maxsize) - dest, dest);
530 #else
531       memset ((void *) &f, 0, sizeof (f));
532       f.__magic = _IOMAGIC;
533       f.__mode.__write = 1;
534       /* The buffer size is one less than MAXLEN
535          so we have space for the null terminator.  */
536       f.__bufp = f.__buffer = (char *) dest;
537       f.__bufsize = (s + maxsize) - dest;
538       f.__put_limit = f.__buffer + f.__bufsize;
539       f.__get_limit = f.__buffer;
540       /* After the buffer is full (MAXLEN characters have been written),
541          any more characters written will go to the bit bucket.  */
542       f.__room_funcs = __default_room_functions;
543       f.__io_funcs.__write = NULL;
544       f.__seen = 1;
545 #endif
546       /* We clear the last available byte so we can find out whether
547          the numeric representation is too long.  */
548       s[maxsize - 1] = '\0';
549
550       info.prec = right_prec;
551       info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
552       info.spec = 'f';
553       info.is_long_double = is_long_double;
554       info.is_short = 0;
555       info.is_long = 0;
556       info.alt = 0;
557       info.space = 0;
558       info.left = left;
559       info.showsign = 0;
560       info.group = group;
561       info.pad = pad;
562       info.extra = 1;           /* This means use values from LC_MONETARY.  */
563       info.wide = 0;
564
565       ptr = &fpnum;
566       done = __printf_fp ((FILE *) &f, &info, &ptr);
567       if (done < 0)
568         {
569           va_end (ap);
570           return -1;
571         }
572
573       if (s[maxsize - 1] != '\0')
574         {
575           __set_errno (E2BIG);
576           return -1;
577         }
578
579       dest += done;
580
581       if (!cs_precedes)
582         {
583           if (sign_posn == 3)
584             {
585               if (sep_by_space == 1)
586                 out_char (' ');
587               out_string (sign_string);
588             }
589
590           if (print_curr_symbol)
591             {
592               if ((sign_posn == 3 && sep_by_space == 2)
593                   || (sign_posn == 4 && sep_by_space == 1)
594                   || (sign_posn == 2 && sep_by_space == 1)
595                   || (sign_posn == 1 && sep_by_space == 1)
596                   || (sign_posn == 0 && sep_by_space == 1))
597                 out_char (space_char);
598               out_nstring (currency_symbol, currency_symbol_len);
599               if (sign_posn == 4)
600                 {
601                   if (sep_by_space == 2)
602                     out_char (' ');
603                   out_string (sign_string);
604                 }
605             }
606         }
607
608       if (sign_posn == 2)
609         {
610           if (sep_by_space == 2)
611             out_char (' ');
612           out_string (sign_string);
613         }
614
615       if (sign_posn == 0 && is_negative)
616         out_char (right_paren);
617
618       /* Now test whether the output width is filled.  */
619       if (dest - startp < width)
620         {
621           if (left)
622             /* We simply have to fill using spaces.  */
623             do
624               out_char (' ');
625             while (dest - startp < width);
626           else
627             {
628               int dist = width - (dest - startp);
629               char *cp;
630               for (cp = dest - 1; cp >= startp; --cp)
631                 cp[dist] = cp[0];
632
633               dest += dist;
634
635               do
636                 startp[--dist] = ' ';
637               while (dist > 0);
638             }
639         }
640     }
641
642   /* Terminate the string.  */
643   *dest = '\0';
644
645   va_end (ap);
646
647   return dest - s;
648 }