| /* __gmp_doprnt -- printf style formatted output. |
| |
| THE FUNCTIONS IN THIS FILE ARE FOR INTERNAL USE ONLY. THEY'RE ALMOST |
| CERTAIN TO BE SUBJECT TO INCOMPATIBLE CHANGES OR DISAPPEAR COMPLETELY IN |
| FUTURE GNU MP RELEASES. |
| |
| Copyright 2001-2003 Free Software Foundation, Inc. |
| |
| This file is part of the GNU MP Library. |
| |
| The GNU MP Library is free software; you can redistribute it and/or modify |
| it under the terms of either: |
| |
| * the GNU Lesser General Public License as published by the Free |
| Software Foundation; either version 3 of the License, or (at your |
| option) any later version. |
| |
| or |
| |
| * the GNU General Public License as published by the Free Software |
| Foundation; either version 2 of the License, or (at your option) any |
| later version. |
| |
| or both in parallel, as here. |
| |
| The GNU MP Library is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| for more details. |
| |
| You should have received copies of the GNU General Public License and the |
| GNU Lesser General Public License along with the GNU MP Library. If not, |
| see https://www.gnu.org/licenses/. */ |
| |
| #define _GNU_SOURCE /* for DECIMAL_POINT in glibc langinfo.h */ |
| |
| #include "config.h" /* needed for the HAVE_, could also move gmp incls */ |
| |
| #include <stdarg.h> |
| #include <ctype.h> /* for isdigit */ |
| #include <stddef.h> /* for ptrdiff_t */ |
| #include <string.h> |
| #include <stdio.h> /* for NULL */ |
| #include <stdlib.h> |
| |
| #if HAVE_INTTYPES_H |
| # include <inttypes.h> /* for intmax_t */ |
| #else |
| # if HAVE_STDINT_H |
| # include <stdint.h> |
| # endif |
| #endif |
| |
| #if HAVE_LANGINFO_H |
| #include <langinfo.h> /* for nl_langinfo */ |
| #endif |
| |
| #if HAVE_LOCALE_H |
| #include <locale.h> /* for localeconv */ |
| #endif |
| |
| #if HAVE_SYS_TYPES_H |
| #include <sys/types.h> /* for quad_t */ |
| #endif |
| |
| #include "gmp-impl.h" |
| |
| |
| /* change this to "#define TRACE(x) x" for diagnostics */ |
| #define TRACE(x) |
| |
| |
| /* Should be portable, but in any case this is only used under some ASSERTs. */ |
| #define va_equal(x, y) \ |
| (memcmp (&(x), &(y), sizeof(va_list)) == 0) |
| |
| |
| /* printf is convenient because it allows various types to be printed in one |
| fairly compact call, so having gmp_printf support the standard types as |
| well as the gmp ones is important. This ends up meaning all the standard |
| parsing must be duplicated, to get a new routine recognising the gmp |
| extras. |
| |
| With the currently favoured handling of mpz etc as Z, Q and F type |
| markers, it's not possible to use glibc register_printf_function since |
| that only accepts new conversion characters, not new types. If Z was a |
| conversion there'd be no way to specify hex, decimal or octal, or |
| similarly with F no way to specify fixed point or scientific format. |
| |
| It seems wisest to pass conversions %f, %e and %g of float, double and |
| long double over to the standard printf. It'd be hard to be sure of |
| getting the right handling for NaNs, rounding, etc. Integer conversions |
| %d etc and string conversions %s on the other hand could be easily enough |
| handled within gmp_doprnt, but if floats are going to libc then it's just |
| as easy to send all non-gmp types there. |
| |
| "Z" was a type marker for size_t in old glibc, but there seems no need to |
| provide access to that now "z" is standard. |
| |
| In GMP 4.1.1 we documented "ll" and "L" as being equivalent, but in C99 |
| in fact "ll" is just for long long and "L" just for long double. |
| Apparently GLIBC allows "L" for long long though. This doesn't affect |
| us as such, since both are passed through to the C library. To be |
| consistent with what we said before, the two are treated equivalently |
| here, and it's left to the C library to do what it thinks with them. |
| |
| Possibilities: |
| |
| "b" might be nice for binary output, and could even be supported for the |
| standard C types too if desired. |
| |
| POSIX style "%n$" parameter numbering would be possible, but would need |
| to be handled completely within gmp_doprnt, since the numbering will be |
| all different once the format string it cut into pieces. |
| |
| Some options for mpq formatting would be good. Perhaps a non-zero |
| precision field could give a width for the denominator and mean always |
| put a "/". A form "n+p/q" might interesting too, though perhaps that's |
| better left to applications. |
| |
| Right now there's no way for an application to know whether types like |
| intmax_t are supported here. If configure is doing its job and the same |
| compiler is used for gmp as for the application then there shouldn't be |
| any problem, but perhaps gmp.h should have some preprocessor symbols to |
| say what libgmp can do. */ |
| |
| |
| |
| /* If a gmp format is the very first thing or there are two gmp formats with |
| nothing in between then we'll reach here with this_fmt == last_fmt and we |
| can do nothing in that case. |
| |
| last_ap is always replaced after a FLUSH, so it doesn't matter if va_list |
| is a call-by-reference and the funs->format routine modifies it. */ |
| |
| #define FLUSH() \ |
| do { \ |
| if (this_fmt == last_fmt) \ |
| { \ |
| TRACE (printf ("nothing to flush\n")); \ |
| ASSERT (va_equal (this_ap, last_ap)); \ |
| } \ |
| else \ |
| { \ |
| ASSERT (*this_fmt == '%'); \ |
| *this_fmt = '\0'; \ |
| TRACE (printf ("flush \"%s\"\n", last_fmt)); \ |
| DOPRNT_FORMAT (last_fmt, last_ap); \ |
| } \ |
| } while (0) |
| |
| |
| /* Parse up the given format string and do the appropriate output using the |
| given "funs" routines. The data parameter is passed through to those |
| routines. */ |
| |
| int |
| __gmp_doprnt (const struct doprnt_funs_t *funs, void *data, |
| const char *orig_fmt, va_list orig_ap) |
| { |
| va_list ap, this_ap, last_ap; |
| size_t alloc_fmt_size, orig_fmt_size; |
| char *fmt, *alloc_fmt, *last_fmt, *this_fmt, *gmp_str; |
| int retval = 0; |
| int type, fchar, *value, seen_precision; |
| struct doprnt_params_t param; |
| |
| TRACE (printf ("gmp_doprnt \"%s\"\n", orig_fmt)); |
| |
| /* Don't modify orig_ap, if va_list is actually an array and hence call by |
| reference. It could be argued that it'd be more efficient to leave the |
| caller to make a copy if it cared, but doing so here is going to be a |
| very small part of the total work, and we may as well keep applications |
| out of trouble. */ |
| va_copy (ap, orig_ap); |
| |
| /* The format string is chopped up into pieces to be passed to |
| funs->format. Unfortunately that means it has to be copied so each |
| piece can be null-terminated. We're not going to be very fast here, so |
| use __gmp_allocate_func rather than TMP_ALLOC, to avoid overflowing the |
| stack if a long output string is given. */ |
| alloc_fmt_size = orig_fmt_size = strlen (orig_fmt) + 1; |
| #if _LONG_LONG_LIMB |
| /* for a long long limb we change %Mx to %llx, so could need an extra 1 |
| char for every 3 existing */ |
| alloc_fmt_size += alloc_fmt_size / 3; |
| #endif |
| alloc_fmt = __GMP_ALLOCATE_FUNC_TYPE (alloc_fmt_size, char); |
| fmt = alloc_fmt; |
| memcpy (fmt, orig_fmt, orig_fmt_size); |
| |
| /* last_fmt and last_ap are just after the last output, and hence where |
| the next output will begin, when that's done */ |
| last_fmt = fmt; |
| va_copy (last_ap, ap); |
| |
| for (;;) |
| { |
| TRACE (printf ("next: \"%s\"\n", fmt)); |
| |
| fmt = strchr (fmt, '%'); |
| if (fmt == NULL) |
| break; |
| |
| /* this_fmt and this_ap are the current '%' sequence being considered */ |
| this_fmt = fmt; |
| va_copy (this_ap, ap); |
| fmt++; /* skip the '%' */ |
| |
| TRACE (printf ("considering\n"); |
| printf (" last: \"%s\"\n", last_fmt); |
| printf (" this: \"%s\"\n", this_fmt)); |
| |
| type = '\0'; |
| value = ¶m.width; |
| |
| param.base = 10; |
| param.conv = 0; |
| param.expfmt = "e%c%02ld"; |
| param.exptimes4 = 0; |
| param.fill = ' '; |
| param.justify = DOPRNT_JUSTIFY_RIGHT; |
| param.prec = 6; |
| param.showbase = DOPRNT_SHOWBASE_NO; |
| param.showpoint = 0; |
| param.showtrailing = 1; |
| param.sign = '\0'; |
| param.width = 0; |
| seen_precision = 0; |
| |
| /* This loop parses a single % sequence. "break" from the switch |
| means continue with this %, "goto next" means the conversion |
| character has been seen and a new % should be sought. */ |
| for (;;) |
| { |
| fchar = *fmt++; |
| if (fchar == '\0') |
| break; |
| |
| switch (fchar) { |
| |
| case 'a': |
| /* %a behaves like %e, but defaults to all significant digits, |
| and there's no leading zeros on the exponent (which is in |
| fact bit-based) */ |
| param.base = 16; |
| param.expfmt = "p%c%ld"; |
| goto conv_a; |
| case 'A': |
| param.base = -16; |
| param.expfmt = "P%c%ld"; |
| conv_a: |
| param.conv = DOPRNT_CONV_SCIENTIFIC; |
| param.exptimes4 = 1; |
| if (! seen_precision) |
| param.prec = -1; /* default to all digits */ |
| param.showbase = DOPRNT_SHOWBASE_YES; |
| param.showtrailing = 1; |
| goto floating_a; |
| |
| case 'c': |
| /* Let's assume wchar_t will be promoted to "int" in the call, |
| the same as char will be. */ |
| (void) va_arg (ap, int); |
| goto next; |
| |
| case 'd': |
| case 'i': |
| case 'u': |
| integer: |
| TRACE (printf ("integer, base=%d\n", param.base)); |
| if (! seen_precision) |
| param.prec = -1; |
| switch (type) { |
| case 'j': |
| /* Let's assume uintmax_t is the same size as intmax_t. */ |
| #if HAVE_INTMAX_T |
| (void) va_arg (ap, intmax_t); |
| #else |
| ASSERT_FAIL (intmax_t not available); |
| #endif |
| break; |
| case 'l': |
| (void) va_arg (ap, long); |
| break; |
| case 'L': |
| #if HAVE_LONG_LONG |
| (void) va_arg (ap, long long); |
| #else |
| ASSERT_FAIL (long long not available); |
| #endif |
| break; |
| case 'N': |
| { |
| mp_ptr xp; |
| mp_size_t xsize, abs_xsize; |
| mpz_t z; |
| FLUSH (); |
| xp = va_arg (ap, mp_ptr); |
| PTR(z) = xp; |
| xsize = (int) va_arg (ap, mp_size_t); |
| abs_xsize = ABS (xsize); |
| MPN_NORMALIZE (xp, abs_xsize); |
| SIZ(z) = (xsize >= 0 ? abs_xsize : -abs_xsize); |
| ASSERT_CODE (ALLOC(z) = abs_xsize); |
| gmp_str = mpz_get_str (NULL, param.base, z); |
| goto gmp_integer; |
| } |
| /* break; */ |
| case 'q': |
| /* quad_t is probably the same as long long, but let's treat |
| it separately just to be sure. Also let's assume u_quad_t |
| will be the same size as quad_t. */ |
| #if HAVE_QUAD_T |
| (void) va_arg (ap, quad_t); |
| #else |
| ASSERT_FAIL (quad_t not available); |
| #endif |
| break; |
| case 'Q': |
| FLUSH (); |
| gmp_str = mpq_get_str (NULL, param.base, va_arg(ap, mpq_srcptr)); |
| goto gmp_integer; |
| case 't': |
| #if HAVE_PTRDIFF_T |
| (void) va_arg (ap, ptrdiff_t); |
| #else |
| ASSERT_FAIL (ptrdiff_t not available); |
| #endif |
| break; |
| case 'z': |
| (void) va_arg (ap, size_t); |
| break; |
| case 'Z': |
| { |
| int ret; |
| FLUSH (); |
| gmp_str = mpz_get_str (NULL, param.base, |
| va_arg (ap, mpz_srcptr)); |
| gmp_integer: |
| ret = __gmp_doprnt_integer (funs, data, ¶m, gmp_str); |
| __GMP_FREE_FUNC_TYPE (gmp_str, strlen(gmp_str)+1, char); |
| DOPRNT_ACCUMULATE (ret); |
| va_copy (last_ap, ap); |
| last_fmt = fmt; |
| } |
| break; |
| default: |
| /* default is an "int", and this includes h=short and hh=char |
| since they're promoted to int in a function call */ |
| (void) va_arg (ap, int); |
| break; |
| } |
| goto next; |
| |
| case 'E': |
| param.base = -10; |
| param.expfmt = "E%c%02ld"; |
| /*FALLTHRU*/ |
| case 'e': |
| param.conv = DOPRNT_CONV_SCIENTIFIC; |
| floating: |
| if (param.showbase == DOPRNT_SHOWBASE_NONZERO) |
| { |
| /* # in %e, %f and %g */ |
| param.showpoint = 1; |
| param.showtrailing = 1; |
| } |
| floating_a: |
| switch (type) { |
| case 'F': |
| FLUSH (); |
| DOPRNT_ACCUMULATE (__gmp_doprnt_mpf (funs, data, ¶m, |
| GMP_DECIMAL_POINT, |
| va_arg (ap, mpf_srcptr))); |
| va_copy (last_ap, ap); |
| last_fmt = fmt; |
| break; |
| case 'L': |
| #if HAVE_LONG_DOUBLE |
| (void) va_arg (ap, long double); |
| #else |
| ASSERT_FAIL (long double not available); |
| #endif |
| break; |
| default: |
| (void) va_arg (ap, double); |
| break; |
| } |
| goto next; |
| |
| case 'f': |
| param.conv = DOPRNT_CONV_FIXED; |
| goto floating; |
| |
| case 'F': /* mpf_t */ |
| case 'j': /* intmax_t */ |
| case 'L': /* long long */ |
| case 'N': /* mpn */ |
| case 'q': /* quad_t */ |
| case 'Q': /* mpq_t */ |
| case 't': /* ptrdiff_t */ |
| case 'z': /* size_t */ |
| case 'Z': /* mpz_t */ |
| set_type: |
| type = fchar; |
| break; |
| |
| case 'G': |
| param.base = -10; |
| param.expfmt = "E%c%02ld"; |
| /*FALLTHRU*/ |
| case 'g': |
| param.conv = DOPRNT_CONV_GENERAL; |
| param.showtrailing = 0; |
| goto floating; |
| |
| case 'h': |
| if (type != 'h') |
| goto set_type; |
| type = 'H'; /* internal code for "hh" */ |
| break; |
| |
| case 'l': |
| if (type != 'l') |
| goto set_type; |
| type = 'L'; /* "ll" means "L" */ |
| break; |
| |
| case 'm': |
| /* glibc strerror(errno), no argument */ |
| goto next; |
| |
| case 'M': /* mp_limb_t */ |
| /* mung format string to l or ll and let plain printf handle it */ |
| #if _LONG_LONG_LIMB |
| memmove (fmt+1, fmt, strlen (fmt)+1); |
| fmt[-1] = 'l'; |
| fmt[0] = 'l'; |
| fmt++; |
| type = 'L'; |
| #else |
| fmt[-1] = 'l'; |
| type = 'l'; |
| #endif |
| break; |
| |
| case 'n': |
| { |
| void *p; |
| FLUSH (); |
| p = va_arg (ap, void *); |
| switch (type) { |
| case '\0': * (int *) p = retval; break; |
| case 'F': mpf_set_si ((mpf_ptr) p, (long) retval); break; |
| case 'H': * (char *) p = retval; break; |
| case 'h': * (short *) p = retval; break; |
| #if HAVE_INTMAX_T |
| case 'j': * (intmax_t *) p = retval; break; |
| #else |
| case 'j': ASSERT_FAIL (intmax_t not available); break; |
| #endif |
| case 'l': * (long *) p = retval; break; |
| #if HAVE_QUAD_T && HAVE_LONG_LONG |
| case 'q': |
| ASSERT_ALWAYS (sizeof (quad_t) == sizeof (long long)); |
| /*FALLTHRU*/ |
| #else |
| case 'q': ASSERT_FAIL (quad_t not available); break; |
| #endif |
| #if HAVE_LONG_LONG |
| case 'L': * (long long *) p = retval; break; |
| #else |
| case 'L': ASSERT_FAIL (long long not available); break; |
| #endif |
| case 'N': |
| { |
| mp_size_t n; |
| n = va_arg (ap, mp_size_t); |
| n = ABS (n); |
| if (n != 0) |
| { |
| * (mp_ptr) p = retval; |
| MPN_ZERO ((mp_ptr) p + 1, n - 1); |
| } |
| } |
| break; |
| case 'Q': mpq_set_si ((mpq_ptr) p, (long) retval, 1L); break; |
| #if HAVE_PTRDIFF_T |
| case 't': * (ptrdiff_t *) p = retval; break; |
| #else |
| case 't': ASSERT_FAIL (ptrdiff_t not available); break; |
| #endif |
| case 'z': * (size_t *) p = retval; break; |
| case 'Z': mpz_set_si ((mpz_ptr) p, (long) retval); break; |
| } |
| } |
| va_copy (last_ap, ap); |
| last_fmt = fmt; |
| goto next; |
| |
| case 'o': |
| param.base = 8; |
| goto integer; |
| |
| case 'p': |
| case 's': |
| /* "void *" will be good enough for "char *" or "wchar_t *", no |
| need for separate code. */ |
| (void) va_arg (ap, const void *); |
| goto next; |
| |
| case 'x': |
| param.base = 16; |
| goto integer; |
| case 'X': |
| param.base = -16; |
| goto integer; |
| |
| case '%': |
| goto next; |
| |
| case '#': |
| param.showbase = DOPRNT_SHOWBASE_NONZERO; |
| break; |
| |
| case '\'': |
| /* glibc digit grouping, just pass it through, no support for it |
| on gmp types */ |
| break; |
| |
| case '+': |
| case ' ': |
| param.sign = fchar; |
| break; |
| |
| case '-': |
| param.justify = DOPRNT_JUSTIFY_LEFT; |
| break; |
| case '.': |
| seen_precision = 1; |
| param.prec = -1; /* "." alone means all necessary digits */ |
| value = ¶m.prec; |
| break; |
| |
| case '*': |
| { |
| int n = va_arg (ap, int); |
| |
| if (value == ¶m.width) |
| { |
| /* negative width means left justify */ |
| if (n < 0) |
| { |
| param.justify = DOPRNT_JUSTIFY_LEFT; |
| n = -n; |
| } |
| param.width = n; |
| } |
| else |
| { |
| /* don't allow negative precision */ |
| param.prec = MAX (0, n); |
| } |
| } |
| break; |
| |
| case '0': |
| if (value == ¶m.width) |
| { |
| /* in width field, set fill */ |
| param.fill = '0'; |
| |
| /* for right justify, put the fill after any minus sign */ |
| if (param.justify == DOPRNT_JUSTIFY_RIGHT) |
| param.justify = DOPRNT_JUSTIFY_INTERNAL; |
| } |
| else |
| { |
| /* in precision field, set value */ |
| *value = 0; |
| } |
| break; |
| |
| case '1': case '2': case '3': case '4': case '5': |
| case '6': case '7': case '8': case '9': |
| /* process all digits to form a value */ |
| { |
| int n = 0; |
| do { |
| n = n * 10 + (fchar-'0'); |
| fchar = *fmt++; |
| } while (isascii (fchar) && isdigit (fchar)); |
| fmt--; /* unget the non-digit */ |
| *value = n; |
| } |
| break; |
| |
| default: |
| /* something invalid */ |
| ASSERT (0); |
| goto next; |
| } |
| } |
| |
| next: |
| /* Stop parsing the current "%" format, look for a new one. */ |
| ; |
| } |
| |
| TRACE (printf ("remainder: \"%s\"\n", last_fmt)); |
| if (*last_fmt != '\0') |
| DOPRNT_FORMAT (last_fmt, last_ap); |
| |
| if (funs->final != NULL) |
| if ((*funs->final) (data) == -1) |
| goto error; |
| |
| done: |
| __GMP_FREE_FUNC_TYPE (alloc_fmt, alloc_fmt_size, char); |
| return retval; |
| |
| error: |
| retval = -1; |
| goto done; |
| } |