]> git.saurik.com Git - apple/libc.git/blame - stdlib/FreeBSD/strfmon.c
Libc-391.tar.gz
[apple/libc.git] / stdlib / FreeBSD / strfmon.c
CommitLineData
9385eb3d
A
1/*-
2 * Copyright (c) 2001 Alexey Zelkin <phantom@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 */
27
28#include <sys/cdefs.h>
29__FBSDID("$FreeBSD: src/lib/libc/stdlib/strfmon.c,v 1.14 2003/03/20 08:18:55 ache Exp $");
30
31#include <sys/types.h>
32#include <ctype.h>
33#include <errno.h>
34#include <limits.h>
35#include <locale.h>
36#include <stdarg.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40
41/* internal flags */
42#define NEED_GROUPING 0x01 /* print digits grouped (default) */
43#define SIGN_POSN_USED 0x02 /* '+' or '(' usage flag */
44#define LOCALE_POSN 0x04 /* use locale defined +/- (default) */
45#define PARENTH_POSN 0x08 /* enclose negative amount in () */
46#define SUPRESS_CURR_SYMBOL 0x10 /* supress the currency from output */
47#define LEFT_JUSTIFY 0x20 /* left justify */
48#define USE_INTL_CURRENCY 0x40 /* use international currency symbol */
49#define IS_NEGATIVE 0x80 /* is argument value negative ? */
50
51/* internal macros */
52#define PRINT(CH) do { \
53 if (dst >= s + maxsize) \
54 goto e2big_error; \
55 *dst++ = CH; \
56} while (0)
57
58#define PRINTS(STR) do { \
59 char *tmps = STR; \
60 while (*tmps != '\0') \
61 PRINT(*tmps++); \
62} while (0)
63
64#define GET_NUMBER(VAR) do { \
65 VAR = 0; \
66 while (isdigit((unsigned char)*fmt)) { \
67 VAR *= 10; \
68 VAR += *fmt - '0'; \
69 fmt++; \
70 } \
71} while (0)
72
73#define GRPCPY(howmany) do { \
74 int i = howmany; \
75 while (i-- > 0) { \
76 avalue_size--; \
77 *--bufend = *(avalue+avalue_size+padded); \
78 } \
79} while (0)
80
81#define GRPSEP do { \
82 *--bufend = thousands_sep; \
83 groups++; \
84} while (0)
85
86static void __setup_vars(int, char *, char *, char *, char **);
87static int __calc_left_pad(int, char *);
88static char *__format_grouped_double(double, int *, int, int, int);
89
90ssize_t
91strfmon(char * __restrict s, size_t maxsize, const char * __restrict format,
92 ...)
93{
94 va_list ap;
95 char *dst; /* output destination pointer */
96 const char *fmt; /* current format poistion pointer */
97 struct lconv *lc; /* pointer to lconv structure */
98 char *asciivalue; /* formatted double pointer */
99
100 int flags; /* formatting options */
101 int pad_char; /* padding character */
102 int pad_size; /* pad size */
103 int width; /* field width */
104 int left_prec; /* left precision */
105 int right_prec; /* right precision */
106 double value; /* just value */
107 char space_char = ' '; /* space after currency */
108
109 char cs_precedes, /* values gathered from struct lconv */
110 sep_by_space,
111 sign_posn,
112 *signstr,
113 *currency_symbol;
114
115 char *tmpptr; /* temporary vars */
116 int sverrno;
117
118 va_start(ap, format);
119
120 lc = localeconv();
121 dst = s;
122 fmt = format;
123 asciivalue = NULL;
124 currency_symbol = NULL;
125 pad_size = 0;
126
127 while (*fmt) {
128 /* pass nonformating characters AS IS */
129 if (*fmt != '%')
130 goto literal;
131
132 /* '%' found ! */
133
134 /* "%%" mean just '%' */
135 if (*(fmt+1) == '%') {
136 fmt++;
137 literal:
138 PRINT(*fmt++);
139 continue;
140 }
141
142 /* set up initial values */
143 flags = (NEED_GROUPING|LOCALE_POSN);
144 pad_char = ' '; /* padding character is "space" */
145 left_prec = -1; /* no left precision specified */
146 right_prec = -1; /* no right precision specified */
147 width = -1; /* no width specified */
148 value = 0; /* we have no value to print now */
149
150 /* Flags */
151 while (1) {
152 switch (*++fmt) {
153 case '=': /* fill character */
154 pad_char = *++fmt;
155 if (pad_char == '\0')
156 goto format_error;
157 continue;
158 case '^': /* not group currency */
159 flags &= ~(NEED_GROUPING);
160 continue;
161 case '+': /* use locale defined signs */
162 if (flags & SIGN_POSN_USED)
163 goto format_error;
164 flags |= (SIGN_POSN_USED|LOCALE_POSN);
165 continue;
166 case '(': /* enclose negatives with () */
167 if (flags & SIGN_POSN_USED)
168 goto format_error;
169 flags |= (SIGN_POSN_USED|PARENTH_POSN);
170 continue;
171 case '!': /* suppress currency symbol */
172 flags |= SUPRESS_CURR_SYMBOL;
173 continue;
174 case '-': /* alignment (left) */
175 flags |= LEFT_JUSTIFY;
176 continue;
177 default:
178 break;
179 }
180 break;
181 }
182
183 /* field Width */
184 if (isdigit((unsigned char)*fmt)) {
185 GET_NUMBER(width);
186 /* Do we have enough space to put number with
187 * required width ?
188 */
189 if (dst + width >= s + maxsize)
190 goto e2big_error;
191 }
192
193 /* Left precision */
194 if (*fmt == '#') {
195 if (!isdigit((unsigned char)*++fmt))
196 goto format_error;
197 GET_NUMBER(left_prec);
198 }
199
200 /* Right precision */
201 if (*fmt == '.') {
202 if (!isdigit((unsigned char)*++fmt))
203 goto format_error;
204 GET_NUMBER(right_prec);
205 }
206
207 /* Conversion Characters */
208 switch (*fmt++) {
209 case 'i': /* use internaltion currency format */
210 flags |= USE_INTL_CURRENCY;
211 break;
212 case 'n': /* use national currency format */
213 flags &= ~(USE_INTL_CURRENCY);
214 break;
215 default: /* required character is missing or
216 premature EOS */
217 goto format_error;
218 }
219
220 if (flags & USE_INTL_CURRENCY) {
221 currency_symbol = strdup(lc->int_curr_symbol);
222 if (currency_symbol != NULL)
223 space_char = *(currency_symbol+3);
224 } else
225 currency_symbol = strdup(lc->currency_symbol);
226
227 if (currency_symbol == NULL)
228 goto end_error; /* ENOMEM. */
229
230 /* value itself */
231 value = va_arg(ap, double);
232
233 /* detect sign */
234 if (value < 0) {
235 flags |= IS_NEGATIVE;
236 value = -value;
237 }
238
239 /* fill left_prec with amount of padding chars */
240 if (left_prec >= 0) {
241 pad_size = __calc_left_pad((flags ^ IS_NEGATIVE),
242 currency_symbol) -
243 __calc_left_pad(flags, currency_symbol);
244 if (pad_size < 0)
245 pad_size = 0;
246 }
247
248 asciivalue = __format_grouped_double(value, &flags,
249 left_prec, right_prec, pad_char);
250 if (asciivalue == NULL)
251 goto end_error; /* errno already set */
252 /* to ENOMEM by malloc() */
253
254 /* set some variables for later use */
255 __setup_vars(flags, &cs_precedes, &sep_by_space,
256 &sign_posn, &signstr);
257
258 /*
259 * Description of some LC_MONETARY's values:
260 *
261 * p_cs_precedes & n_cs_precedes
262 *
263 * = 1 - $currency_symbol precedes the value
264 * for a monetary quantity with a non-negative value
265 * = 0 - symbol succeeds the value
266 *
267 * p_sep_by_space & n_sep_by_space
268 *
269 * = 0 - no space separates $currency_symbol
270 * from the value for a monetary quantity with a
271 * non-negative value
272 * = 1 - space separates the symbol from the value
273 * = 2 - space separates the symbol and the sign string,
274 * if adjacent.
275 *
276 * p_sign_posn & n_sign_posn
277 *
278 * = 0 - parentheses enclose the quantity and the
279 * $currency_symbol
280 * = 1 - the sign string precedes the quantity and the
281 * $currency_symbol
282 * = 2 - the sign string succeeds the quantity and the
283 * $currency_symbol
284 * = 3 - the sign string precedes the $currency_symbol
285 * = 4 - the sign string succeeds the $currency_symbol
286 *
287 */
288
289 tmpptr = dst;
290
291 while (pad_size-- > 0)
292 PRINT(' ');
293
294 if (sign_posn == 0 && (flags & IS_NEGATIVE))
295 PRINT('(');
296
297 if (cs_precedes == 1) {
298 if (sign_posn == 1 || sign_posn == 3) {
299 PRINTS(signstr);
300 if (sep_by_space == 2) /* XXX: ? */
301 PRINT(' ');
302 }
303
304 if (!(flags & SUPRESS_CURR_SYMBOL)) {
305 PRINTS(currency_symbol);
306
307 if (sign_posn == 4) {
308 if (sep_by_space == 2)
309 PRINT(space_char);
310 PRINTS(signstr);
311 if (sep_by_space == 1)
312 PRINT(' ');
313 } else if (sep_by_space == 1)
314 PRINT(space_char);
315 }
316 } else if (sign_posn == 1)
317 PRINTS(signstr);
318
319 PRINTS(asciivalue);
320
321 if (cs_precedes == 0) {
322 if (sign_posn == 3) {
323 if (sep_by_space == 1)
324 PRINT(' ');
325 PRINTS(signstr);
326 }
327
328 if (!(flags & SUPRESS_CURR_SYMBOL)) {
329 if ((sign_posn == 3 && sep_by_space == 2)
330 || (sep_by_space == 1
331 && (sign_posn == 0
332 || sign_posn == 1
333 || sign_posn == 2
334 || sign_posn == 4)))
335 PRINT(space_char);
336 PRINTS(currency_symbol); /* XXX: len */
337 if (sign_posn == 4) {
338 if (sep_by_space == 2)
339 PRINT(' ');
340 PRINTS(signstr);
341 }
342 }
343 }
344
345 if (sign_posn == 2) {
346 if (sep_by_space == 2)
347 PRINT(' ');
348 PRINTS(signstr);
349 }
350
351 if (sign_posn == 0 && (flags & IS_NEGATIVE))
352 PRINT(')');
353
354 if (dst - tmpptr < width) {
355 if (flags & LEFT_JUSTIFY) {
356 while (dst - tmpptr < width)
357 PRINT(' ');
358 } else {
359 pad_size = dst-tmpptr;
360 memmove(tmpptr + width-pad_size, tmpptr,
361 pad_size);
362 memset(tmpptr, ' ', width-pad_size);
363 dst += width-pad_size;
364 }
365 }
366 }
367
368 PRINT('\0');
369 va_end(ap);
370 free(asciivalue);
371 free(currency_symbol);
372 return (dst - s - 1); /* return size of put data except trailing '\0' */
373
374e2big_error:
375 errno = E2BIG;
376 goto end_error;
377
378format_error:
379 errno = EINVAL;
380
381end_error:
382 sverrno = errno;
383 if (asciivalue != NULL)
384 free(asciivalue);
385 if (currency_symbol != NULL)
386 free(currency_symbol);
387 errno = sverrno;
388 va_end(ap);
389 return (-1);
390}
391
392static void
393__setup_vars(int flags, char *cs_precedes, char *sep_by_space,
394 char *sign_posn, char **signstr) {
395
396 struct lconv *lc = localeconv();
397
398 if ((flags & IS_NEGATIVE) && (flags & USE_INTL_CURRENCY)) {
399 *cs_precedes = lc->int_n_cs_precedes;
400 *sep_by_space = lc->int_n_sep_by_space;
401 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->int_n_sign_posn;
402 *signstr = (lc->negative_sign == '\0') ? "-"
403 : lc->negative_sign;
404 } else if (flags & USE_INTL_CURRENCY) {
405 *cs_precedes = lc->int_p_cs_precedes;
406 *sep_by_space = lc->int_p_sep_by_space;
407 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->int_p_sign_posn;
408 *signstr = lc->positive_sign;
409 } else if (flags & IS_NEGATIVE) {
410 *cs_precedes = lc->n_cs_precedes;
411 *sep_by_space = lc->n_sep_by_space;
412 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->n_sign_posn;
413 *signstr = (lc->negative_sign == '\0') ? "-"
414 : lc->negative_sign;
415 } else {
416 *cs_precedes = lc->p_cs_precedes;
417 *sep_by_space = lc->p_sep_by_space;
418 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->p_sign_posn;
419 *signstr = lc->positive_sign;
420 }
421
422 /* Set defult values for unspecified information. */
423 if (*cs_precedes != 0)
424 *cs_precedes = 1;
425 if (*sep_by_space == CHAR_MAX)
426 *sep_by_space = 0;
427 if (*sign_posn == CHAR_MAX)
428 *sign_posn = 0;
429}
430
431static int
432__calc_left_pad(int flags, char *cur_symb) {
433
434 char cs_precedes, sep_by_space, sign_posn, *signstr;
435 int left_chars = 0;
436
437 __setup_vars(flags, &cs_precedes, &sep_by_space, &sign_posn, &signstr);
438
439 if (cs_precedes != 0) {
440 left_chars += strlen(cur_symb);
441 if (sep_by_space != 0)
442 left_chars++;
443 }
444
445 switch (sign_posn) {
446 case 1:
447 left_chars += strlen(signstr);
448 break;
449 case 3:
450 case 4:
451 if (cs_precedes != 0)
452 left_chars += strlen(signstr);
453 }
454 return (left_chars);
455}
456
457static int
458get_groups(int size, char *grouping) {
459
460 int chars = 0;
461
462 if (*grouping == CHAR_MAX || *grouping <= 0) /* no grouping ? */
463 return (0);
464
465 while (size > (int)*grouping) {
466 chars++;
467 size -= (int)*grouping++;
468 /* no more grouping ? */
469 if (*grouping == CHAR_MAX)
470 break;
471 /* rest grouping with same value ? */
472 if (*grouping == 0) {
473 chars += (size - 1) / *(grouping - 1);
474 break;
475 }
476 }
477 return (chars);
478}
479
480/* convert double to ASCII */
481static char *
482__format_grouped_double(double value, int *flags,
483 int left_prec, int right_prec, int pad_char) {
484
485 char *rslt;
486 char *avalue;
487 int avalue_size;
488 char fmt[32];
489
490 size_t bufsize;
491 char *bufend;
492
493 int padded;
494
495 struct lconv *lc = localeconv();
496 char *grouping;
497 char decimal_point;
498 char thousands_sep;
499
500 int groups = 0;
501
502 grouping = lc->mon_grouping;
503 decimal_point = *lc->mon_decimal_point;
504 if (decimal_point == '\0')
505 decimal_point = *lc->decimal_point;
506 thousands_sep = *lc->mon_thousands_sep;
507 if (thousands_sep == '\0')
508 thousands_sep = *lc->thousands_sep;
509
510 /* fill left_prec with default value */
511 if (left_prec == -1)
512 left_prec = 0;
513
514 /* fill right_prec with default value */
515 if (right_prec == -1) {
516 if (*flags & USE_INTL_CURRENCY)
517 right_prec = lc->int_frac_digits;
518 else
519 right_prec = lc->frac_digits;
520
521 if (right_prec == CHAR_MAX) /* POSIX locale ? */
522 right_prec = 2;
523 }
524
525 if (*flags & NEED_GROUPING)
526 left_prec += get_groups(left_prec, grouping);
527
528 /* convert to string */
529 snprintf(fmt, sizeof(fmt), "%%%d.%df", left_prec + right_prec + 1,
530 right_prec);
531 avalue_size = asprintf(&avalue, fmt, value);
532 if (avalue_size < 0)
533 return (NULL);
534
535 /* make sure that we've enough space for result string */
536 bufsize = strlen(avalue)*2+1;
537 rslt = malloc(bufsize);
538 if (rslt == NULL) {
539 free(avalue);
540 return (NULL);
541 }
542 memset(rslt, 0, bufsize);
543 bufend = rslt + bufsize - 1; /* reserve space for trailing '\0' */
544
545 /* skip spaces at beggining */
546 padded = 0;
547 while (avalue[padded] == ' ') {
548 padded++;
549 avalue_size--;
550 }
551
552 if (right_prec > 0) {
553 bufend -= right_prec;
554 memcpy(bufend, avalue + avalue_size+padded-right_prec,
555 right_prec);
556 *--bufend = decimal_point;
557 avalue_size -= (right_prec + 1);
558 }
559
560 if ((*flags & NEED_GROUPING) &&
561 thousands_sep != '\0' && /* XXX: need investigation */
562 *grouping != CHAR_MAX &&
563 *grouping > 0) {
564 while (avalue_size > (int)*grouping) {
565 GRPCPY(*grouping);
566 GRPSEP;
567 grouping++;
568
569 /* no more grouping ? */
570 if (*grouping == CHAR_MAX)
571 break;
572
573 /* rest grouping with same value ? */
574 if (*grouping == 0) {
575 grouping--;
576 while (avalue_size > *grouping) {
577 GRPCPY(*grouping);
578 GRPSEP;
579 }
580 }
581 }
582 if (avalue_size != 0)
583 GRPCPY(avalue_size);
584 padded -= groups;
585
586 } else {
587 bufend -= avalue_size;
588 memcpy(bufend, avalue+padded, avalue_size);
589 if (right_prec == 0)
590 padded--; /* decrease assumed $decimal_point */
591 }
592
593 /* do padding with pad_char */
594 if (padded > 0) {
595 bufend -= padded;
596 memset(bufend, pad_char, padded);
597 }
598
599 bufsize = bufsize - (bufend - rslt) + 1;
600 memmove(rslt, bufend, bufsize);
601 free(avalue);
602 return (rslt);
603}