blob: fa3f3029f95df0f7c7579bcfd55f19da2af747ca [file] [log] [blame]
Austin Schuhbb1338c2024-06-15 19:31:16 -07001/* __gmp_doprnt -- printf style formatted output.
2
3 THE FUNCTIONS IN THIS FILE ARE FOR INTERNAL USE ONLY. THEY'RE ALMOST
4 CERTAIN TO BE SUBJECT TO INCOMPATIBLE CHANGES OR DISAPPEAR COMPLETELY IN
5 FUTURE GNU MP RELEASES.
6
7Copyright 2001-2003 Free Software Foundation, Inc.
8
9This file is part of the GNU MP Library.
10
11The GNU MP Library is free software; you can redistribute it and/or modify
12it under the terms of either:
13
14 * the GNU Lesser General Public License as published by the Free
15 Software Foundation; either version 3 of the License, or (at your
16 option) any later version.
17
18or
19
20 * the GNU General Public License as published by the Free Software
21 Foundation; either version 2 of the License, or (at your option) any
22 later version.
23
24or both in parallel, as here.
25
26The GNU MP Library is distributed in the hope that it will be useful, but
27WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
28or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
29for more details.
30
31You should have received copies of the GNU General Public License and the
32GNU Lesser General Public License along with the GNU MP Library. If not,
33see https://www.gnu.org/licenses/. */
34
35#define _GNU_SOURCE /* for DECIMAL_POINT in glibc langinfo.h */
36
37#include "config.h" /* needed for the HAVE_, could also move gmp incls */
38
39#include <stdarg.h>
40#include <ctype.h> /* for isdigit */
41#include <stddef.h> /* for ptrdiff_t */
42#include <string.h>
43#include <stdio.h> /* for NULL */
44#include <stdlib.h>
45
46#if HAVE_INTTYPES_H
47# include <inttypes.h> /* for intmax_t */
48#else
49# if HAVE_STDINT_H
50# include <stdint.h>
51# endif
52#endif
53
54#if HAVE_LANGINFO_H
55#include <langinfo.h> /* for nl_langinfo */
56#endif
57
58#if HAVE_LOCALE_H
59#include <locale.h> /* for localeconv */
60#endif
61
62#if HAVE_SYS_TYPES_H
63#include <sys/types.h> /* for quad_t */
64#endif
65
66#include "gmp-impl.h"
67
68
69/* change this to "#define TRACE(x) x" for diagnostics */
70#define TRACE(x)
71
72
73/* Should be portable, but in any case this is only used under some ASSERTs. */
74#define va_equal(x, y) \
75 (memcmp (&(x), &(y), sizeof(va_list)) == 0)
76
77
78/* printf is convenient because it allows various types to be printed in one
79 fairly compact call, so having gmp_printf support the standard types as
80 well as the gmp ones is important. This ends up meaning all the standard
81 parsing must be duplicated, to get a new routine recognising the gmp
82 extras.
83
84 With the currently favoured handling of mpz etc as Z, Q and F type
85 markers, it's not possible to use glibc register_printf_function since
86 that only accepts new conversion characters, not new types. If Z was a
87 conversion there'd be no way to specify hex, decimal or octal, or
88 similarly with F no way to specify fixed point or scientific format.
89
90 It seems wisest to pass conversions %f, %e and %g of float, double and
91 long double over to the standard printf. It'd be hard to be sure of
92 getting the right handling for NaNs, rounding, etc. Integer conversions
93 %d etc and string conversions %s on the other hand could be easily enough
94 handled within gmp_doprnt, but if floats are going to libc then it's just
95 as easy to send all non-gmp types there.
96
97 "Z" was a type marker for size_t in old glibc, but there seems no need to
98 provide access to that now "z" is standard.
99
100 In GMP 4.1.1 we documented "ll" and "L" as being equivalent, but in C99
101 in fact "ll" is just for long long and "L" just for long double.
102 Apparently GLIBC allows "L" for long long though. This doesn't affect
103 us as such, since both are passed through to the C library. To be
104 consistent with what we said before, the two are treated equivalently
105 here, and it's left to the C library to do what it thinks with them.
106
107 Possibilities:
108
109 "b" might be nice for binary output, and could even be supported for the
110 standard C types too if desired.
111
112 POSIX style "%n$" parameter numbering would be possible, but would need
113 to be handled completely within gmp_doprnt, since the numbering will be
114 all different once the format string it cut into pieces.
115
116 Some options for mpq formatting would be good. Perhaps a non-zero
117 precision field could give a width for the denominator and mean always
118 put a "/". A form "n+p/q" might interesting too, though perhaps that's
119 better left to applications.
120
121 Right now there's no way for an application to know whether types like
122 intmax_t are supported here. If configure is doing its job and the same
123 compiler is used for gmp as for the application then there shouldn't be
124 any problem, but perhaps gmp.h should have some preprocessor symbols to
125 say what libgmp can do. */
126
127
128
129/* If a gmp format is the very first thing or there are two gmp formats with
130 nothing in between then we'll reach here with this_fmt == last_fmt and we
131 can do nothing in that case.
132
133 last_ap is always replaced after a FLUSH, so it doesn't matter if va_list
134 is a call-by-reference and the funs->format routine modifies it. */
135
136#define FLUSH() \
137 do { \
138 if (this_fmt == last_fmt) \
139 { \
140 TRACE (printf ("nothing to flush\n")); \
141 ASSERT (va_equal (this_ap, last_ap)); \
142 } \
143 else \
144 { \
145 ASSERT (*this_fmt == '%'); \
146 *this_fmt = '\0'; \
147 TRACE (printf ("flush \"%s\"\n", last_fmt)); \
148 DOPRNT_FORMAT (last_fmt, last_ap); \
149 } \
150 } while (0)
151
152
153/* Parse up the given format string and do the appropriate output using the
154 given "funs" routines. The data parameter is passed through to those
155 routines. */
156
157int
158__gmp_doprnt (const struct doprnt_funs_t *funs, void *data,
159 const char *orig_fmt, va_list orig_ap)
160{
161 va_list ap, this_ap, last_ap;
162 size_t alloc_fmt_size, orig_fmt_size;
163 char *fmt, *alloc_fmt, *last_fmt, *this_fmt, *gmp_str;
164 int retval = 0;
165 int type, fchar, *value, seen_precision;
166 struct doprnt_params_t param;
167
168 TRACE (printf ("gmp_doprnt \"%s\"\n", orig_fmt));
169
170 /* Don't modify orig_ap, if va_list is actually an array and hence call by
171 reference. It could be argued that it'd be more efficient to leave the
172 caller to make a copy if it cared, but doing so here is going to be a
173 very small part of the total work, and we may as well keep applications
174 out of trouble. */
175 va_copy (ap, orig_ap);
176
177 /* The format string is chopped up into pieces to be passed to
178 funs->format. Unfortunately that means it has to be copied so each
179 piece can be null-terminated. We're not going to be very fast here, so
180 use __gmp_allocate_func rather than TMP_ALLOC, to avoid overflowing the
181 stack if a long output string is given. */
182 alloc_fmt_size = orig_fmt_size = strlen (orig_fmt) + 1;
183#if _LONG_LONG_LIMB
184 /* for a long long limb we change %Mx to %llx, so could need an extra 1
185 char for every 3 existing */
186 alloc_fmt_size += alloc_fmt_size / 3;
187#endif
188 alloc_fmt = __GMP_ALLOCATE_FUNC_TYPE (alloc_fmt_size, char);
189 fmt = alloc_fmt;
190 memcpy (fmt, orig_fmt, orig_fmt_size);
191
192 /* last_fmt and last_ap are just after the last output, and hence where
193 the next output will begin, when that's done */
194 last_fmt = fmt;
195 va_copy (last_ap, ap);
196
197 for (;;)
198 {
199 TRACE (printf ("next: \"%s\"\n", fmt));
200
201 fmt = strchr (fmt, '%');
202 if (fmt == NULL)
203 break;
204
205 /* this_fmt and this_ap are the current '%' sequence being considered */
206 this_fmt = fmt;
207 va_copy (this_ap, ap);
208 fmt++; /* skip the '%' */
209
210 TRACE (printf ("considering\n");
211 printf (" last: \"%s\"\n", last_fmt);
212 printf (" this: \"%s\"\n", this_fmt));
213
214 type = '\0';
215 value = &param.width;
216
217 param.base = 10;
218 param.conv = 0;
219 param.expfmt = "e%c%02ld";
220 param.exptimes4 = 0;
221 param.fill = ' ';
222 param.justify = DOPRNT_JUSTIFY_RIGHT;
223 param.prec = 6;
224 param.showbase = DOPRNT_SHOWBASE_NO;
225 param.showpoint = 0;
226 param.showtrailing = 1;
227 param.sign = '\0';
228 param.width = 0;
229 seen_precision = 0;
230
231 /* This loop parses a single % sequence. "break" from the switch
232 means continue with this %, "goto next" means the conversion
233 character has been seen and a new % should be sought. */
234 for (;;)
235 {
236 fchar = *fmt++;
237 if (fchar == '\0')
238 break;
239
240 switch (fchar) {
241
242 case 'a':
243 /* %a behaves like %e, but defaults to all significant digits,
244 and there's no leading zeros on the exponent (which is in
245 fact bit-based) */
246 param.base = 16;
247 param.expfmt = "p%c%ld";
248 goto conv_a;
249 case 'A':
250 param.base = -16;
251 param.expfmt = "P%c%ld";
252 conv_a:
253 param.conv = DOPRNT_CONV_SCIENTIFIC;
254 param.exptimes4 = 1;
255 if (! seen_precision)
256 param.prec = -1; /* default to all digits */
257 param.showbase = DOPRNT_SHOWBASE_YES;
258 param.showtrailing = 1;
259 goto floating_a;
260
261 case 'c':
262 /* Let's assume wchar_t will be promoted to "int" in the call,
263 the same as char will be. */
264 (void) va_arg (ap, int);
265 goto next;
266
267 case 'd':
268 case 'i':
269 case 'u':
270 integer:
271 TRACE (printf ("integer, base=%d\n", param.base));
272 if (! seen_precision)
273 param.prec = -1;
274 switch (type) {
275 case 'j':
276 /* Let's assume uintmax_t is the same size as intmax_t. */
277#if HAVE_INTMAX_T
278 (void) va_arg (ap, intmax_t);
279#else
280 ASSERT_FAIL (intmax_t not available);
281#endif
282 break;
283 case 'l':
284 (void) va_arg (ap, long);
285 break;
286 case 'L':
287#if HAVE_LONG_LONG
288 (void) va_arg (ap, long long);
289#else
290 ASSERT_FAIL (long long not available);
291#endif
292 break;
293 case 'N':
294 {
295 mp_ptr xp;
296 mp_size_t xsize, abs_xsize;
297 mpz_t z;
298 FLUSH ();
299 xp = va_arg (ap, mp_ptr);
300 PTR(z) = xp;
301 xsize = (int) va_arg (ap, mp_size_t);
302 abs_xsize = ABS (xsize);
303 MPN_NORMALIZE (xp, abs_xsize);
304 SIZ(z) = (xsize >= 0 ? abs_xsize : -abs_xsize);
305 ASSERT_CODE (ALLOC(z) = abs_xsize);
306 gmp_str = mpz_get_str (NULL, param.base, z);
307 goto gmp_integer;
308 }
309 /* break; */
310 case 'q':
311 /* quad_t is probably the same as long long, but let's treat
312 it separately just to be sure. Also let's assume u_quad_t
313 will be the same size as quad_t. */
314#if HAVE_QUAD_T
315 (void) va_arg (ap, quad_t);
316#else
317 ASSERT_FAIL (quad_t not available);
318#endif
319 break;
320 case 'Q':
321 FLUSH ();
322 gmp_str = mpq_get_str (NULL, param.base, va_arg(ap, mpq_srcptr));
323 goto gmp_integer;
324 case 't':
325#if HAVE_PTRDIFF_T
326 (void) va_arg (ap, ptrdiff_t);
327#else
328 ASSERT_FAIL (ptrdiff_t not available);
329#endif
330 break;
331 case 'z':
332 (void) va_arg (ap, size_t);
333 break;
334 case 'Z':
335 {
336 int ret;
337 FLUSH ();
338 gmp_str = mpz_get_str (NULL, param.base,
339 va_arg (ap, mpz_srcptr));
340 gmp_integer:
341 ret = __gmp_doprnt_integer (funs, data, &param, gmp_str);
342 __GMP_FREE_FUNC_TYPE (gmp_str, strlen(gmp_str)+1, char);
343 DOPRNT_ACCUMULATE (ret);
344 va_copy (last_ap, ap);
345 last_fmt = fmt;
346 }
347 break;
348 default:
349 /* default is an "int", and this includes h=short and hh=char
350 since they're promoted to int in a function call */
351 (void) va_arg (ap, int);
352 break;
353 }
354 goto next;
355
356 case 'E':
357 param.base = -10;
358 param.expfmt = "E%c%02ld";
359 /*FALLTHRU*/
360 case 'e':
361 param.conv = DOPRNT_CONV_SCIENTIFIC;
362 floating:
363 if (param.showbase == DOPRNT_SHOWBASE_NONZERO)
364 {
365 /* # in %e, %f and %g */
366 param.showpoint = 1;
367 param.showtrailing = 1;
368 }
369 floating_a:
370 switch (type) {
371 case 'F':
372 FLUSH ();
373 DOPRNT_ACCUMULATE (__gmp_doprnt_mpf (funs, data, &param,
374 GMP_DECIMAL_POINT,
375 va_arg (ap, mpf_srcptr)));
376 va_copy (last_ap, ap);
377 last_fmt = fmt;
378 break;
379 case 'L':
380#if HAVE_LONG_DOUBLE
381 (void) va_arg (ap, long double);
382#else
383 ASSERT_FAIL (long double not available);
384#endif
385 break;
386 default:
387 (void) va_arg (ap, double);
388 break;
389 }
390 goto next;
391
392 case 'f':
393 param.conv = DOPRNT_CONV_FIXED;
394 goto floating;
395
396 case 'F': /* mpf_t */
397 case 'j': /* intmax_t */
398 case 'L': /* long long */
399 case 'N': /* mpn */
400 case 'q': /* quad_t */
401 case 'Q': /* mpq_t */
402 case 't': /* ptrdiff_t */
403 case 'z': /* size_t */
404 case 'Z': /* mpz_t */
405 set_type:
406 type = fchar;
407 break;
408
409 case 'G':
410 param.base = -10;
411 param.expfmt = "E%c%02ld";
412 /*FALLTHRU*/
413 case 'g':
414 param.conv = DOPRNT_CONV_GENERAL;
415 param.showtrailing = 0;
416 goto floating;
417
418 case 'h':
419 if (type != 'h')
420 goto set_type;
421 type = 'H'; /* internal code for "hh" */
422 break;
423
424 case 'l':
425 if (type != 'l')
426 goto set_type;
427 type = 'L'; /* "ll" means "L" */
428 break;
429
430 case 'm':
431 /* glibc strerror(errno), no argument */
432 goto next;
433
434 case 'M': /* mp_limb_t */
435 /* mung format string to l or ll and let plain printf handle it */
436#if _LONG_LONG_LIMB
437 memmove (fmt+1, fmt, strlen (fmt)+1);
438 fmt[-1] = 'l';
439 fmt[0] = 'l';
440 fmt++;
441 type = 'L';
442#else
443 fmt[-1] = 'l';
444 type = 'l';
445#endif
446 break;
447
448 case 'n':
449 {
450 void *p;
451 FLUSH ();
452 p = va_arg (ap, void *);
453 switch (type) {
454 case '\0': * (int *) p = retval; break;
455 case 'F': mpf_set_si ((mpf_ptr) p, (long) retval); break;
456 case 'H': * (char *) p = retval; break;
457 case 'h': * (short *) p = retval; break;
458#if HAVE_INTMAX_T
459 case 'j': * (intmax_t *) p = retval; break;
460#else
461 case 'j': ASSERT_FAIL (intmax_t not available); break;
462#endif
463 case 'l': * (long *) p = retval; break;
464#if HAVE_QUAD_T && HAVE_LONG_LONG
465 case 'q':
466 ASSERT_ALWAYS (sizeof (quad_t) == sizeof (long long));
467 /*FALLTHRU*/
468#else
469 case 'q': ASSERT_FAIL (quad_t not available); break;
470#endif
471#if HAVE_LONG_LONG
472 case 'L': * (long long *) p = retval; break;
473#else
474 case 'L': ASSERT_FAIL (long long not available); break;
475#endif
476 case 'N':
477 {
478 mp_size_t n;
479 n = va_arg (ap, mp_size_t);
480 n = ABS (n);
481 if (n != 0)
482 {
483 * (mp_ptr) p = retval;
484 MPN_ZERO ((mp_ptr) p + 1, n - 1);
485 }
486 }
487 break;
488 case 'Q': mpq_set_si ((mpq_ptr) p, (long) retval, 1L); break;
489#if HAVE_PTRDIFF_T
490 case 't': * (ptrdiff_t *) p = retval; break;
491#else
492 case 't': ASSERT_FAIL (ptrdiff_t not available); break;
493#endif
494 case 'z': * (size_t *) p = retval; break;
495 case 'Z': mpz_set_si ((mpz_ptr) p, (long) retval); break;
496 }
497 }
498 va_copy (last_ap, ap);
499 last_fmt = fmt;
500 goto next;
501
502 case 'o':
503 param.base = 8;
504 goto integer;
505
506 case 'p':
507 case 's':
508 /* "void *" will be good enough for "char *" or "wchar_t *", no
509 need for separate code. */
510 (void) va_arg (ap, const void *);
511 goto next;
512
513 case 'x':
514 param.base = 16;
515 goto integer;
516 case 'X':
517 param.base = -16;
518 goto integer;
519
520 case '%':
521 goto next;
522
523 case '#':
524 param.showbase = DOPRNT_SHOWBASE_NONZERO;
525 break;
526
527 case '\'':
528 /* glibc digit grouping, just pass it through, no support for it
529 on gmp types */
530 break;
531
532 case '+':
533 case ' ':
534 param.sign = fchar;
535 break;
536
537 case '-':
538 param.justify = DOPRNT_JUSTIFY_LEFT;
539 break;
540 case '.':
541 seen_precision = 1;
542 param.prec = -1; /* "." alone means all necessary digits */
543 value = &param.prec;
544 break;
545
546 case '*':
547 {
548 int n = va_arg (ap, int);
549
550 if (value == &param.width)
551 {
552 /* negative width means left justify */
553 if (n < 0)
554 {
555 param.justify = DOPRNT_JUSTIFY_LEFT;
556 n = -n;
557 }
558 param.width = n;
559 }
560 else
561 {
562 /* don't allow negative precision */
563 param.prec = MAX (0, n);
564 }
565 }
566 break;
567
568 case '0':
569 if (value == &param.width)
570 {
571 /* in width field, set fill */
572 param.fill = '0';
573
574 /* for right justify, put the fill after any minus sign */
575 if (param.justify == DOPRNT_JUSTIFY_RIGHT)
576 param.justify = DOPRNT_JUSTIFY_INTERNAL;
577 }
578 else
579 {
580 /* in precision field, set value */
581 *value = 0;
582 }
583 break;
584
585 case '1': case '2': case '3': case '4': case '5':
586 case '6': case '7': case '8': case '9':
587 /* process all digits to form a value */
588 {
589 int n = 0;
590 do {
591 n = n * 10 + (fchar-'0');
592 fchar = *fmt++;
593 } while (isascii (fchar) && isdigit (fchar));
594 fmt--; /* unget the non-digit */
595 *value = n;
596 }
597 break;
598
599 default:
600 /* something invalid */
601 ASSERT (0);
602 goto next;
603 }
604 }
605
606 next:
607 /* Stop parsing the current "%" format, look for a new one. */
608 ;
609 }
610
611 TRACE (printf ("remainder: \"%s\"\n", last_fmt));
612 if (*last_fmt != '\0')
613 DOPRNT_FORMAT (last_fmt, last_ap);
614
615 if (funs->final != NULL)
616 if ((*funs->final) (data) == -1)
617 goto error;
618
619 done:
620 __GMP_FREE_FUNC_TYPE (alloc_fmt, alloc_fmt_size, char);
621 return retval;
622
623 error:
624 retval = -1;
625 goto done;
626}