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