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