select: Fix "warning: no previous prototype for function".
[gnulib.git] / lib / strfmon_l.c
blob781276f368fa998c7a539e9f33f83c8d521d7588
1 /* strfmon_l override.
2 Copyright (C) 2017-2020 Free Software Foundation, Inc.
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, or (at your option)
7 any later version.
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.
14 You should have received a copy of the GNU General Public License along
15 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 <stdbool.h>
26 #include <stdlib.h>
27 #include <string.h>
29 #undef strfmon_l
31 /* This override can only support a limited number of arguments. */
32 #define MAX_ARGS 16
34 /* A parsed directive. */
35 typedef struct
37 bool needs_long_double;
38 const char *conversion_ptr;
40 directive_t;
42 /* A parsed format string. */
43 typedef struct
45 size_t count;
46 directive_t dir[MAX_ARGS];
48 directives_t;
50 /* Parses a monetary format string.
51 Returns 0 and fills *DIRECTIVESP if valid.
52 Returns -1 if invalid. */
53 static int
54 fmon_parse (const char *format, directives_t *directivesp)
56 size_t count = 0;
57 const char *cp = format;
59 while (*cp != '\0')
61 if (*cp++ == '%')
63 /* Parse flags. */
64 while (*cp == '=' || *cp == '^' || *cp == '+' || *cp == '('
65 || *cp == '!' || *cp == '-')
67 if (*cp == '=')
69 cp++;
70 if (*cp == '\0')
71 return -1;
73 cp++;
75 /* Parse field width. */
76 while (*cp >= '0' && *cp <= '9')
77 cp++;
78 /* Parse left precision. */
79 if (*cp == '#')
81 cp++;
82 while (*cp >= '0' && *cp <= '9')
83 cp++;
85 /* Parse right precision. */
86 if (*cp == '.')
88 cp++;
89 while (*cp >= '0' && *cp <= '9')
90 cp++;
92 /* Now comes the conversion specifier. */
93 if (*cp != '%')
95 if (count == MAX_ARGS)
96 /* Too many arguments. */
97 return -1;
99 /* glibc supports an 'L' modifier before the conversion specifier. */
100 if (*cp == 'L')
102 cp++;
103 directivesp->dir[count].needs_long_double = true;
105 else
106 directivesp->dir[count].needs_long_double = false;
107 if (!(*cp == 'i' || *cp == 'n'))
108 return -1;
109 directivesp->dir[count].conversion_ptr = cp;
110 count++;
112 cp++;
116 directivesp->count = count;
117 return 0;
120 ssize_t
121 rpl_strfmon_l (char *s, size_t maxsize, locale_t locale, const char *format, ...)
123 /* Work around glibc 2.23 bug
124 <https://sourceware.org/bugzilla/show_bug.cgi?id=19633>. */
125 va_list argptr;
126 locale_t orig_locale;
127 directives_t directives;
128 ssize_t result;
130 orig_locale = uselocale ((locale_t)0);
132 if (uselocale (locale) == (locale_t)0)
133 /* errno is set. */
134 return -1;
136 /* The format string may consume 'double' or 'long double' arguments.
137 In order not to have to link with libffcall or libffi, convert all
138 arguments to 'long double', and use a modified format string that
139 requests 'long double' arguments. But since 'long double' arguments
140 are only supported on glibc, do so only if the original format string
141 consumes at least one 'long double' argument. */
142 if (fmon_parse (format, &directives) < 0)
144 errno = EINVAL;
145 result = -1;
147 else
149 bool use_long_double;
150 unsigned int i;
152 use_long_double = false;
153 for (i = 0; i < directives.count; i++)
154 if (directives.dir[i].needs_long_double)
156 use_long_double = true;
157 break;
160 va_start (argptr, format);
162 if (use_long_double)
164 char *ld_format;
166 /* Allocate room for the modified format string. */
167 ld_format = (char *) malloc (strlen (format) + directives.count + 1);
168 if (ld_format == NULL)
170 errno = ENOMEM;
171 result = -1;
173 else
175 long double args[MAX_ARGS];
177 /* Create the modified format string. */
179 const char *p = format;
180 char *dest = ld_format;
181 for (i = 0; i < directives.count; i++)
183 const char *q = directives.dir[i].conversion_ptr;
184 memcpy (dest, p, q - p);
185 dest += q - p;
186 if (!directives.dir[i].needs_long_double)
187 *dest++ = 'L';
188 p = q;
190 strcpy (dest, p);
193 /* Set up arguments array. */
194 for (i = 0; i < directives.count; i++)
195 args[i] = (directives.dir[i].needs_long_double
196 ? va_arg (argptr, long double)
197 : (long double) va_arg (argptr, double));
198 /* Avoid uninitialized memory references. */
199 for (; i < MAX_ARGS; i++)
200 args[i] = 0.0L;
202 result = strfmon_l (s, maxsize, locale, ld_format,
203 args[0], args[1], args[2], args[3], args[4],
204 args[5], args[6], args[7], args[8], args[9],
205 args[10], args[11], args[12], args[13],
206 args[14], args[15]);
208 free (ld_format);
211 else
213 double args[MAX_ARGS];
215 /* Set up arguments array. */
216 for (i = 0; i < directives.count; i++)
217 args[i] = va_arg (argptr, double);
218 /* Avoid uninitialized memory references. */
219 for (; i < MAX_ARGS; i++)
220 args[i] = 0.0;
222 result = strfmon_l (s, maxsize, locale, format,
223 args[0], args[1], args[2], args[3], args[4],
224 args[5], args[6], args[7], args[8], args[9],
225 args[10], args[11], args[12], args[13], args[14],
226 args[15]);
229 va_end (argptr);
232 if (uselocale (orig_locale) == (locale_t)0)
233 /* errno is set. */
234 return -1;
236 return result;