exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / strfmon_l.c
blob9ade044d4f3983b4f6f1f249f8ace3acc2e7320c
1 /* strfmon_l override.
2 Copyright (C) 2017-2024 Free Software Foundation, Inc.
4 This file is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 2.1 of the
7 License, or (at your option) any later version.
9 This file 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 Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 #include <config.h>
19 /* Specification. */
20 #include <monetary.h>
22 #include <errno.h>
23 #include <locale.h>
24 #include <stdarg.h>
25 #include <stdlib.h>
26 #include <string.h>
28 #undef strfmon_l
30 /* This override can only support a limited number of arguments. */
31 #define MAX_ARGS 16
33 /* A parsed directive. */
34 typedef struct
36 bool needs_long_double;
37 const char *conversion_ptr;
39 directive_t;
41 /* A parsed format string. */
42 typedef struct
44 size_t count;
45 directive_t dir[MAX_ARGS];
47 directives_t;
49 /* Parses a monetary format string.
50 Returns 0 and fills *DIRECTIVESP if valid.
51 Returns -1 if invalid. */
52 static int
53 fmon_parse (const char *format, directives_t *directivesp)
55 size_t count = 0;
56 const char *cp = format;
58 while (*cp != '\0')
60 if (*cp++ == '%')
62 /* Parse flags. */
63 while (*cp == '=' || *cp == '^' || *cp == '+' || *cp == '('
64 || *cp == '!' || *cp == '-')
66 if (*cp == '=')
68 cp++;
69 if (*cp == '\0')
70 return -1;
72 cp++;
74 /* Parse field width. */
75 while (*cp >= '0' && *cp <= '9')
76 cp++;
77 /* Parse left precision. */
78 if (*cp == '#')
80 cp++;
81 while (*cp >= '0' && *cp <= '9')
82 cp++;
84 /* Parse right precision. */
85 if (*cp == '.')
87 cp++;
88 while (*cp >= '0' && *cp <= '9')
89 cp++;
91 /* Now comes the conversion specifier. */
92 if (*cp != '%')
94 if (count == MAX_ARGS)
95 /* Too many arguments. */
96 return -1;
98 /* glibc supports an 'L' modifier before the conversion specifier. */
99 if (*cp == 'L')
101 cp++;
102 directivesp->dir[count].needs_long_double = true;
104 else
105 directivesp->dir[count].needs_long_double = false;
106 if (!(*cp == 'i' || *cp == 'n'))
107 return -1;
108 directivesp->dir[count].conversion_ptr = cp;
109 count++;
111 cp++;
115 directivesp->count = count;
116 return 0;
119 ssize_t
120 rpl_strfmon_l (char *s, size_t maxsize, locale_t locale, const char *format, ...)
122 /* Work around glibc 2.23 bug
123 <https://sourceware.org/bugzilla/show_bug.cgi?id=19633>. */
124 va_list argptr;
125 locale_t orig_locale;
126 directives_t directives;
127 ssize_t result;
129 orig_locale = uselocale ((locale_t)0);
131 if (uselocale (locale) == (locale_t)0)
132 /* errno is set. */
133 return -1;
135 /* The format string may consume 'double' or 'long double' arguments.
136 In order not to have to link with libffcall or libffi, convert all
137 arguments to 'long double', and use a modified format string that
138 requests 'long double' arguments. But since 'long double' arguments
139 are only supported on glibc, do so only if the original format string
140 consumes at least one 'long double' argument. */
141 if (fmon_parse (format, &directives) < 0)
143 errno = EINVAL;
144 result = -1;
146 else
148 bool use_long_double;
149 unsigned int i;
151 use_long_double = false;
152 for (i = 0; i < directives.count; i++)
153 if (directives.dir[i].needs_long_double)
155 use_long_double = true;
156 break;
159 va_start (argptr, format);
161 if (use_long_double)
163 char *ld_format;
165 /* Allocate room for the modified format string. */
166 ld_format = (char *) malloc (strlen (format) + directives.count + 1);
167 if (ld_format == NULL)
169 errno = ENOMEM;
170 result = -1;
172 else
174 long double args[MAX_ARGS];
176 /* Create the modified format string. */
178 const char *p = format;
179 char *dest = ld_format;
180 for (i = 0; i < directives.count; i++)
182 const char *q = directives.dir[i].conversion_ptr;
183 memcpy (dest, p, q - p);
184 dest += q - p;
185 if (!directives.dir[i].needs_long_double)
186 *dest++ = 'L';
187 p = q;
189 strcpy (dest, p);
192 /* Set up arguments array. */
193 for (i = 0; i < directives.count; i++)
194 args[i] = (directives.dir[i].needs_long_double
195 ? va_arg (argptr, long double)
196 : (long double) va_arg (argptr, double));
197 /* Avoid uninitialized memory references. */
198 for (; i < MAX_ARGS; i++)
199 args[i] = 0.0L;
201 result = strfmon_l (s, maxsize, locale, ld_format,
202 args[0], args[1], args[2], args[3], args[4],
203 args[5], args[6], args[7], args[8], args[9],
204 args[10], args[11], args[12], args[13],
205 args[14], args[15]);
207 free (ld_format);
210 else
212 double args[MAX_ARGS];
214 /* Set up arguments array. */
215 for (i = 0; i < directives.count; i++)
216 args[i] = va_arg (argptr, double);
217 /* Avoid uninitialized memory references. */
218 for (; i < MAX_ARGS; i++)
219 args[i] = 0.0;
221 result = strfmon_l (s, maxsize, locale, format,
222 args[0], args[1], args[2], args[3], args[4],
223 args[5], args[6], args[7], args[8], args[9],
224 args[10], args[11], args[12], args[13], args[14],
225 args[15]);
228 va_end (argptr);
231 if (uselocale (orig_locale) == (locale_t)0)
232 /* errno is set. */
233 return -1;
235 return result;