Skip to main content

malachite_base/strings/
gmp_format.rs

1// Copyright © 2026 Mikhail Hogrefe
2//
3// Uses code adopted from the GNU MP Library.
4//
5//      Copyright © 1993-2019 Free Software Foundation, Inc.
6//
7// This file is part of Malachite.
8//
9// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
10// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
11// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
12
13use crate::num::conversion::traits::{ToStringBase, WrappingFrom};
14use alloc::string::String;
15use alloc::vec::Vec;
16use core::cmp::max;
17
18/// A single parsed `printf`-style conversion specification, as GMP's and MPFR's formatted-output
19/// functions understand them: `% [flags] [width] [.precision] [type] [rounding] conv`.
20///
21/// The struct is purely syntactic: it records what was written, and each [`GmpFormatArg`]
22/// implementation applies its own library's interpretation. In particular:
23/// - `sign` is the *last* `+` or space flag written (or 0 for neither), which is what GMP's own
24///   types use, while `plus` and `space` record whether each flag appeared at all, which is what
25///   the C conversions need (`+` overrides space in C, regardless of order).
26/// - `prec` is [`None`] when no precision was written, and `Some(-1)` for a `.` with no digits,
27///   which GMP reads as "all necessary digits" and C and MPFR read as 0.
28/// - `type_chr` is the type or length-modifier character (`Z`, `Q`, `R`, `l`, `h`, and so on, or 0
29///   for none), with `type_doubled` distinguishing `hh` and `ll`. As in GMP's parser, a later type
30///   character overwrites an earlier one.
31/// - `rnd_chr` is MPFR's rounding character, only ever set directly after an `R`.
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct GmpConversionSpec {
34    pub sign: u8,
35    pub plus: bool,
36    pub space: bool,
37    pub alt: bool,
38    pub left: bool,
39    pub group: bool,
40    pub fill: u8,
41    pub width: i64,
42    pub prec: Option<i64>,
43    pub type_chr: u8,
44    pub type_doubled: bool,
45    pub rnd_chr: u8,
46    pub conv: u8,
47}
48
49// Reads a run of decimal digits (the first of which is `c`) from the front of `fmt`, returning the
50// value and the unconsumed tail. GMP stores widths and precisions in a C `int`, so values beyond
51// `i32::MAX` cannot be expressed in a format string and are rejected.
52fn read_digits(c: u8, mut fmt: &[u8]) -> Option<(i64, &[u8])> {
53    let mut n = i64::from(c - b'0');
54    while let Some((&d, tail)) = fmt.split_first()
55        && d.is_ascii_digit()
56    {
57        n = n.checked_mul(10)?.checked_add(i64::from(d - b'0'))?;
58        fmt = tail;
59    }
60    if n > const { i32::MAX as i64 } {
61        return None;
62    }
63    Some((n, fmt))
64}
65
66/// Parses a single conversion specification, starting just after the `%`, and returns it along with
67/// the unconsumed tail. `star` supplies the value of a `*` field width or precision, from the
68/// argument list; it may fail, and a caller with no argument list simply passes `&mut || None`.
69///
70/// The parser mirrors GMP's single-pass structure, including its quirks: flags may appear after the
71/// width, a later `+` or space flag overwrites the `sign` of an earlier one, a later type character
72/// overwrites an earlier one, and a `-` flag does not reset the `0` flag's fill character. A
73/// negative `*` width means left justification, and a negative `*` precision is treated as 0.
74/// Returns [`None`] if a width or precision overflows the range of a C `int` (beyond which GMP
75/// cannot express them), if `star` fails, or on a character with no role in a conversion
76/// specification.
77///
78/// # Worst-case complexity
79/// $T(n) = O(n)$
80///
81/// $M(n) = O(1)$
82///
83/// where $T$ is time, $M$ is additional memory, and $n$ is `fmt.len()`.
84///
85/// # Examples
86/// ```
87/// use malachite_base::strings::gmp_format::parse_gmp_conversion_spec;
88///
89/// let (spec, rest) = parse_gmp_conversion_spec(b"+8.3Zd tail", &mut || None).unwrap();
90/// assert_eq!(spec.sign, b'+');
91/// assert_eq!(spec.width, 8);
92/// assert_eq!(spec.prec, Some(3));
93/// assert_eq!(spec.type_chr, b'Z');
94/// assert_eq!(spec.conv, b'd');
95/// assert_eq!(rest, b" tail");
96/// ```
97///
98/// This is the format-parsing loop of `__gmp_doprnt` from `printf/doprnt.c`, GMP 6.3.0, with MPFR's
99/// `R` type and rounding characters from `vasprintf.c`, MPFR 4.2.2.
100pub fn parse_gmp_conversion_spec<'a>(
101    mut fmt: &'a [u8],
102    star: &mut dyn FnMut() -> Option<i64>,
103) -> Option<(GmpConversionSpec, &'a [u8])> {
104    let mut spec = GmpConversionSpec {
105        sign: 0,
106        plus: false,
107        space: false,
108        alt: false,
109        left: false,
110        group: false,
111        fill: b' ',
112        width: 0,
113        prec: None,
114        type_chr: 0,
115        type_doubled: false,
116        rnd_chr: 0,
117        conv: 0,
118    };
119    let mut in_width = true;
120    loop {
121        let (&c, tail) = fmt.split_first()?;
122        fmt = tail;
123        match c {
124            b'#' => spec.alt = true,
125            b'\'' => spec.group = true,
126            b'+' => {
127                spec.plus = true;
128                spec.sign = c;
129            }
130            b' ' => {
131                spec.space = true;
132                spec.sign = c;
133            }
134            b'-' => spec.left = true,
135            b'0' => {
136                if in_width {
137                    // in the width field, `0` is a flag setting the fill
138                    spec.fill = b'0';
139                } else {
140                    spec.prec = Some(0);
141                }
142            }
143            b'1'..=b'9' => {
144                let (n, tail) = read_digits(c, fmt)?;
145                fmt = tail;
146                if in_width {
147                    spec.width = n;
148                } else {
149                    spec.prec = Some(n);
150                }
151            }
152            b'.' => {
153                // `.` alone is `Some(-1)`; any following digits overwrite it
154                spec.prec = Some(-1);
155                in_width = false;
156            }
157            b'*' => {
158                let n = star()?;
159                if n.unsigned_abs() > const { i32::MAX as u64 } {
160                    return None;
161                }
162                if in_width {
163                    spec.width = if n < 0 {
164                        // a negative width means left justification
165                        spec.left = true;
166                        -n
167                    } else {
168                        n
169                    };
170                } else {
171                    // a negative precision is not allowed
172                    spec.prec = Some(max(0, n));
173                }
174            }
175            b'h' | b'l' => {
176                spec.type_chr = c;
177                spec.type_doubled = false;
178                if let Some((&d, tail)) = fmt.split_first()
179                    && d == c
180                {
181                    spec.type_doubled = true;
182                    fmt = tail;
183                }
184            }
185            b'j' | b'q' | b't' | b'z' | b'L' | b'Q' | b'M' | b'N' | b'Z' | b'P' => {
186                spec.type_chr = c;
187                spec.type_doubled = false;
188            }
189            b'R' => {
190                spec.type_chr = c;
191                spec.type_doubled = false;
192                // MPFR's rounding character comes directly after the `R`; `*`, which in C fetches
193                // the mode from the argument list, is not supported
194                if let Some((&d, tail)) = fmt.split_first() {
195                    match d {
196                        b'N' | b'D' | b'U' | b'Y' | b'Z' => {
197                            spec.rnd_chr = d;
198                            fmt = tail;
199                        }
200                        b'*' => return None,
201                        _ => {}
202                    }
203                }
204            }
205            b'F' => {
206                if spec.type_chr == b'R' {
207                    // after an `R`, `F` is MPFR's uppercase fixed-point conversion
208                    spec.conv = c;
209                    return Some((spec, fmt));
210                }
211                // elsewhere it is GMP's `mpf_t` type character
212                spec.type_chr = c;
213                spec.type_doubled = false;
214            }
215            b'd' | b'i' | b'u' | b'o' | b'x' | b'X' | b'e' | b'E' | b'f' | b'g' | b'G' | b'a'
216            | b'A' | b'b' | b'c' | b's' | b'p' | b'n' | b'm' => {
217                spec.conv = c;
218                return Some((spec, fmt));
219            }
220            _ => return None,
221        }
222    }
223}
224
225/// A value that can be consumed by a conversion of a GMP-style format string; see [`gmp_format`].
226///
227/// Each implementation accepts the conversions its library counterpart would: `Natural` and
228/// `Integer` take `%Z` integer conversions, `Rational` takes `%Q`, `Float` takes `%R`, primitive
229/// integers take the plain C integer conversions (and `%c`), [`char`] takes `%c`, and strings take
230/// `%s`. [`gmp_format`](GmpFormatArg::gmp_format) returns the formatted piece, or [`None`] when the
231/// specification does not apply to the value's type.
232pub trait GmpFormatArg {
233    /// Formats this value according to a single parsed conversion specification, or returns
234    /// [`None`] when the specification does not apply to this type.
235    fn gmp_format(&self, spec: &GmpConversionSpec) -> Option<String>;
236
237    /// The integer consumed by a `*` field width or precision, when this value is a primitive
238    /// integer that fits in an `i64`.
239    fn printf_int(&self) -> Option<i64> {
240        None
241    }
242}
243
244// Appends `n` copies of `fill` to `out`.
245fn pad(out: &mut Vec<u8>, fill: u8, n: usize) {
246    out.resize(out.len() + n, fill);
247}
248
249// Applies the field width of `spec` to the already-rendered `body`, left- or right-justifying it.
250// Used by the conversions whose zero-fill handling is trivial (`%c`, `%s`): the fill is always a
251// space, as in C.
252fn justify(body: &[u8], spec: &GmpConversionSpec) -> Option<String> {
253    let width = usize::try_from(spec.width).unwrap_or(0);
254    let padding = width.saturating_sub(body.len());
255    let mut out = Vec::with_capacity(body.len() + padding);
256    if !spec.left {
257        pad(&mut out, b' ', padding);
258    }
259    out.extend_from_slice(body);
260    if spec.left {
261        pad(&mut out, b' ', padding);
262    }
263    // `body` comes from a `str` or is ASCII
264    String::from_utf8(out).ok()
265}
266
267// Whether `spec` is an integer conversion with no type character or with a C length modifier, which
268// is accepted but has no effect: the value is formatted as passed, and is never truncated the way
269// C's `%hd` would truncate an `int` argument.
270const fn is_c_integer_spec(spec: &GmpConversionSpec) -> bool {
271    matches!(spec.conv, b'd' | b'i' | b'u' | b'o' | b'x' | b'X')
272        && matches!(
273            spec.type_chr,
274            0 | b'h' | b'l' | b'j' | b'q' | b't' | b'z' | b'L'
275        )
276}
277
278// Formats a primitive integer with sign `neg` and absolute-value digits produced by `to_base`,
279// following the C `printf` rules: `+` overrides the space flag, the `0` flag is ignored with left
280// justification or an explicit precision, a `#` prefix is applied only when the digits do not
281// already begin with a zero, and a zero value with a precision of 0 produces no digits. In the C
282// locale the `'` flag groups nothing, so it is accepted and ignored.
283//
284// This is the behavior `gmp_printf` gets by handing the standard conversions to the C library.
285fn format_c_integer(
286    neg: bool,
287    to_base: &dyn Fn(u8, bool) -> String,
288    spec: &GmpConversionSpec,
289) -> Option<String> {
290    if !is_c_integer_spec(spec) {
291        return None;
292    }
293    let digits = match spec.conv {
294        b'o' => to_base(8, false),
295        b'x' => to_base(16, false),
296        b'X' => to_base(16, true),
297        _ => to_base(10, false),
298    };
299    let mut s = digits.as_bytes();
300    let sign = if neg {
301        b'-'
302    } else if spec.plus {
303        b'+'
304    } else if spec.space {
305        b' '
306    } else {
307        0
308    };
309    let sign_len = usize::from(sign != 0);
310    // C reads a `.` with no digits (`Some(-1)`) as a precision of 0
311    let prec = spec.prec.map_or(-1, |p| max(0, p));
312    if prec == 0 && s == b"0" {
313        s = b"";
314    }
315    let mut showbase: &[u8] = if spec.alt {
316        match spec.conv {
317            b'x' => b"0x",
318            b'X' => b"0X",
319            b'o' => b"0",
320            _ => b"",
321        }
322    } else {
323        b""
324    };
325    if s.first() == Some(&b'0') {
326        showbase = b"";
327    }
328    let zeros = usize::try_from(max(0, prec - i64::try_from(s.len()).ok()?)).ok()?;
329    let core = sign_len + showbase.len() + zeros + s.len();
330    let width = usize::try_from(spec.width).unwrap_or(0);
331    let padding = width.saturating_sub(core);
332    // the 0 flag is ignored with left justification or an explicit precision
333    let zero_fill = spec.fill == b'0' && !spec.left && spec.prec.is_none();
334    let mut out = Vec::with_capacity(core + padding);
335    if !spec.left && !zero_fill {
336        pad(&mut out, b' ', padding);
337    }
338    if sign != 0 {
339        out.push(sign);
340    }
341    out.extend_from_slice(showbase);
342    if zero_fill {
343        pad(&mut out, b'0', padding);
344    }
345    pad(&mut out, b'0', zeros);
346    out.extend_from_slice(s);
347    if spec.left {
348        pad(&mut out, b' ', padding);
349    }
350    // ASCII by construction
351    String::from_utf8(out).ok()
352}
353
354// Formats a primitive integer for a `%c` conversion, as C does: the value is converted to an
355// `unsigned char`, keeping its lowest byte.
356fn format_c_char_of_int(value: u64, spec: &GmpConversionSpec) -> Option<String> {
357    if spec.conv != b'c' || !matches!(spec.type_chr, 0 | b'h' | b'l') {
358        return None;
359    }
360    justify(&[u8::wrapping_from(value)], spec)
361}
362
363macro_rules! impl_gmp_format_arg_unsigned {
364    ($t:ident) => {
365        impl GmpFormatArg for $t {
366            /// Formats an unsigned primitive integer according to a single parsed conversion
367            /// specification: a plain C integer conversion (`d`, `i`, `u`, `o`, `x`, or `X`, with
368            /// any C length modifier accepted but not truncating the value), or `c` (keeping the
369            /// value's lowest byte, as C does).
370            ///
371            /// # Worst-case complexity
372            /// $T(n, w, p) = O(n + w + p)$
373            ///
374            /// $M(n, w, p) = O(n + w + p)$
375            ///
376            /// where $T$ is time, $M$ is additional memory, $n$ is `self.significant_bits()`, $w$
377            /// is the field width stored in `spec`, and $p$ is the precision stored in `spec`:
378            /// rendering the digits is linear in the value's bits, and padding to the field width
379            /// or precision is linear in those settings.
380            ///
381            /// # Examples
382            /// See [here](super::gmp_format#gmp_format).
383            fn gmp_format(&self, spec: &GmpConversionSpec) -> Option<String> {
384                if spec.conv == b'c' {
385                    return format_c_char_of_int(u64::wrapping_from(*self), spec);
386                }
387                format_c_integer(
388                    false,
389                    &|base, upper| {
390                        if upper {
391                            self.to_string_base_upper(base)
392                        } else {
393                            self.to_string_base(base)
394                        }
395                    },
396                    spec,
397                )
398            }
399
400            fn printf_int(&self) -> Option<i64> {
401                i64::try_from(*self).ok()
402            }
403        }
404    };
405}
406apply_to_unsigneds!(impl_gmp_format_arg_unsigned);
407
408macro_rules! impl_gmp_format_arg_signed {
409    ($t:ident) => {
410        impl GmpFormatArg for $t {
411            /// Formats a signed primitive integer according to a single parsed conversion
412            /// specification: a plain C integer conversion (`d`, `i`, `u`, `o`, `x`, or `X`, with
413            /// any C length modifier accepted but not truncating the value, and a negative value
414            /// keeping its sign under every conversion), or `c` (keeping the value's lowest byte,
415            /// as C does).
416            ///
417            /// # Worst-case complexity
418            /// $T(n, w, p) = O(n + w + p)$
419            ///
420            /// $M(n, w, p) = O(n + w + p)$
421            ///
422            /// where $T$ is time, $M$ is additional memory, $n$ is `self.significant_bits()`, $w$
423            /// is the field width stored in `spec`, and $p$ is the precision stored in `spec`:
424            /// rendering the digits is linear in the value's bits, and padding to the field width
425            /// or precision is linear in those settings.
426            ///
427            /// # Examples
428            /// See [here](super::gmp_format#gmp_format).
429            fn gmp_format(&self, spec: &GmpConversionSpec) -> Option<String> {
430                if spec.conv == b'c' {
431                    return format_c_char_of_int(u64::wrapping_from(self.unsigned_abs()), spec);
432                }
433                let abs = self.unsigned_abs();
434                format_c_integer(
435                    *self < 0,
436                    &|base, upper| {
437                        if upper {
438                            abs.to_string_base_upper(base)
439                        } else {
440                            abs.to_string_base(base)
441                        }
442                    },
443                    spec,
444                )
445            }
446
447            fn printf_int(&self) -> Option<i64> {
448                i64::try_from(*self).ok()
449            }
450        }
451    };
452}
453apply_to_signeds!(impl_gmp_format_arg_signed);
454
455impl GmpFormatArg for char {
456    /// Formats a [`char`] according to a single parsed conversion specification, which must be a
457    /// `%c` conversion with no type character.
458    ///
459    /// # Worst-case complexity
460    /// $T(w) = O(w)$
461    ///
462    /// $M(w) = O(w)$
463    ///
464    /// where $T$ is time, $M$ is additional memory, and $w$ is the field width stored in `spec`:
465    /// the character itself is constant-size, but the output is padded to the field width.
466    ///
467    /// # Examples
468    /// See [here](super::gmp_format#gmp_format).
469    fn gmp_format(&self, spec: &GmpConversionSpec) -> Option<String> {
470        if spec.conv != b'c' || spec.type_chr != 0 {
471            return None;
472        }
473        let mut buf = [0; 4];
474        justify(self.encode_utf8(&mut buf).as_bytes(), spec)
475    }
476}
477
478impl GmpFormatArg for &str {
479    /// Formats a string according to a single parsed conversion specification, which must be a `%s`
480    /// conversion with no type character. As in C, the precision is the maximum number of bytes
481    /// written; if that limit would split a multi-byte character, [`None`] is returned, since the
482    /// output could not be a valid string.
483    ///
484    /// # Worst-case complexity
485    /// $T(n) = O(n)$
486    ///
487    /// $M(n) = O(n)$
488    ///
489    /// where $T$ is time, $M$ is additional memory, and $n$ is `max(self.len(), w)`, with `w` the
490    /// field width requested by the format string.
491    ///
492    /// # Examples
493    /// See [here](super::gmp_format#gmp_format).
494    fn gmp_format(&self, spec: &GmpConversionSpec) -> Option<String> {
495        if spec.conv != b's' || spec.type_chr != 0 {
496            return None;
497        }
498        let mut s = *self;
499        if let Some(prec) = spec.prec {
500            let prec = usize::try_from(max(0, prec)).ok()?;
501            if prec < s.len() {
502                if !s.is_char_boundary(prec) {
503                    return None;
504                }
505                s = &s[..prec];
506            }
507        }
508        justify(s.as_bytes(), spec)
509    }
510}
511
512impl GmpFormatArg for String {
513    /// Formats a string according to a single parsed conversion specification; see the
514    /// [`&str`](GmpFormatArg#impl-GmpFormatArg-for-%26str) implementation.
515    ///
516    /// # Worst-case complexity
517    /// $T(n) = O(n)$
518    ///
519    /// $M(n) = O(n)$
520    ///
521    /// where $T$ is time, $M$ is additional memory, and $n$ is `max(self.len(), w)`, with `w` the
522    /// field width requested by the format string.
523    ///
524    /// # Examples
525    /// See [here](super::gmp_format#gmp_format).
526    #[inline]
527    fn gmp_format(&self, spec: &GmpConversionSpec) -> Option<String> {
528        (&**self).gmp_format(spec)
529    }
530}
531
532/// Formats a sequence of values according to a GMP-style `printf` format string, each conversion
533/// consuming the next value, as `gmp_printf` and `mpfr_printf` do.
534///
535/// The format string may contain literal text, `%%` escapes, and any number of conversions, each
536/// written `%[flags][width][.precision][type][rounding]conv`. Which conversions a value accepts is
537/// up to its [`GmpFormatArg`] implementation: `%Z` integer conversions for `Natural` and `Integer`,
538/// `%Q` for `Rational`, `%R` float conversions for `Float`, and the plain C conversions for
539/// primitive integers (`d`, `i`, `u`, `o`, `x`, `X`, `c`), [`char`]s (`c`), and strings (`s`). A
540/// `*` field width or precision consumes the next value, which must be a primitive integer; a
541/// negative `*` width means left justification.
542///
543/// Returns [`None`] when a conversion specification is malformed or requests a width or precision
544/// beyond `i32::MAX` (the range of the C `int` GMP itself stores them in), when a conversion does
545/// not apply to the value it would consume, when there are too few values (extra values are
546/// permitted, as in C), or when the conversion is one this function does not support: `%n`, `%p`,
547/// `%m`, `%F` (GMP's `mpf_t`), `%M` and `%N` (limbs), `%P` (MPFR precisions), and the C float
548/// conversions on primitive floats (use a `Float`).
549///
550/// The [`gmp_format!`](crate::gmp_format) macro wraps this function, building the argument slice.
551///
552/// # Worst-case complexity
553/// $T(n) = O(n (\log n)^2 \log\log n)$
554///
555/// $M(n) = O(n \log n)$
556///
557/// where $T$ is time, $M$ is additional memory, and $n$ is the sum over the conversions of
558/// `max(x.significant_bits(), p, w)`, with `p` and `w` each conversion's precision and field width,
559/// plus `fmt.len()`.
560///
561/// # Examples
562/// ```
563/// use malachite_base::strings::gmp_format::gmp_format;
564///
565/// assert_eq!(
566///     gmp_format("%d + %d = %d", &[&2u32, &2u32, &4u32]),
567///     Some("2 + 2 = 4".to_string())
568/// );
569/// assert_eq!(
570///     gmp_format("%c%s%c", &[&'(', &"hello", &')']),
571///     Some("(hello)".to_string())
572/// );
573/// assert_eq!(
574///     gmp_format("%0*x", &[&8i32, &255u32]),
575///     Some("000000ff".to_string())
576/// );
577/// // 100% literal
578/// assert_eq!(gmp_format("100%%", &[]), Some("100%".to_string()));
579///
580/// // a conversion that does not apply to its value
581/// assert_eq!(gmp_format("%s", &[&5u32]), None);
582/// // too few values
583/// assert_eq!(gmp_format("%d %d", &[&5u32]), None);
584/// ```
585///
586/// This is `gmp_snprintf` from `printf/snprintf.c`, GMP 6.3.0, and `mpfr_snprintf` from
587/// `vasprintf.c`, MPFR 4.2.2, where the buffer is always large enough.
588pub fn gmp_format(fmt: &str, args: &[&dyn GmpFormatArg]) -> Option<String> {
589    let bytes = fmt.as_bytes();
590    let mut out = Vec::new();
591    let mut i = 0;
592    let mut next = 0;
593    while i < bytes.len() {
594        if bytes[i] == b'%' {
595            if bytes.get(i + 1) == Some(&b'%') {
596                out.push(b'%');
597                i += 2;
598                continue;
599            }
600            let (spec, rest) = {
601                let mut star = || {
602                    let arg = args.get(next)?;
603                    next += 1;
604                    arg.printf_int()
605                };
606                parse_gmp_conversion_spec(&bytes[i + 1..], &mut star)?
607            };
608            let arg = args.get(next)?;
609            next += 1;
610            out.extend_from_slice(arg.gmp_format(&spec)?.as_bytes());
611            i = bytes.len() - rest.len();
612        } else {
613            out.push(bytes[i]);
614            i += 1;
615        }
616    }
617    // Literal text is copied byte-for-byte from the input `&str` and every conversion's output is a
618    // `String`, so the output is valid UTF-8.
619    String::from_utf8(out).ok()
620}
621
622/// Formats values according to a GMP-style `printf` format string, as
623/// [`gmp_format`](crate::gmp_format) does, taking the values as ordinary arguments:
624/// `gmp_format!("%Zd of %d", n, k)`.
625///
626/// The result is an `Option<String>`; see [`gmp_format`](crate::gmp_format) for the supported
627/// conversions and failure conditions.
628///
629/// # Examples
630/// ```
631/// use malachite_base::gmp_format;
632///
633/// assert_eq!(
634///     gmp_format!("%d + %d = %d", 2u32, 2u32, 4u32),
635///     Some("2 + 2 = 4".to_string())
636/// );
637/// ```
638#[macro_export]
639macro_rules! gmp_format {
640    ($fmt:expr $(, $args:expr)* $(,)?) => {
641        $crate::strings::gmp_format::gmp_format(
642            $fmt,
643            &[$(&$args as &dyn $crate::strings::gmp_format::GmpFormatArg),*],
644        )
645    };
646}