Skip to main content

rustpython_common/
format.rs

1// spell-checker:ignore ddfe DTSF
2use core::ops::Deref;
3use core::{cmp, str::FromStr};
4use itertools::{Itertools, PeekingNext};
5use malachite_base::num::basic::floats::PrimitiveFloat;
6use malachite_bigint::{BigInt, Sign};
7use num_complex::Complex64;
8use num_traits::FromPrimitive;
9use num_traits::{Signed, cast::ToPrimitive};
10use rustpython_literal::float;
11use rustpython_literal::format::Case;
12
13use crate::wtf8::{CodePoint, Wtf8, Wtf8Buf};
14
15/// Locale information for 'n' format specifier.
16/// Contains thousands separator, decimal point, and grouping pattern
17/// from the C library's `localeconv()`.
18#[derive(Clone, Debug)]
19pub struct LocaleInfo {
20    pub thousands_sep: String,
21    pub decimal_point: String,
22    /// Grouping pattern from `lconv.grouping`.
23    /// Each element is a group size. The last non-zero element repeats.
24    /// e.g. `[3, 0]` means groups of 3 repeating forever.
25    pub grouping: Vec<u8>,
26}
27
28trait FormatParse {
29    fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8)
30    where
31        Self: Sized;
32}
33
34#[derive(Debug, Copy, Clone, PartialEq, Eq)]
35#[repr(u8)]
36pub enum FormatConversion {
37    Str = b's',
38    Repr = b'r',
39    Ascii = b'b',
40}
41
42impl FormatConversion {
43    #[must_use]
44    pub fn from_char(c: CodePoint) -> Option<Self> {
45        match c.to_char_lossy() {
46            's' => Some(Self::Str),
47            'r' => Some(Self::Repr),
48            'a' => Some(Self::Ascii),
49            _ => None,
50        }
51    }
52}
53
54#[derive(Debug, Copy, Clone, PartialEq, Eq)]
55pub enum FormatAlign {
56    Left,
57    Right,
58    AfterSign,
59    Center,
60}
61
62impl FormatAlign {
63    fn from_char(c: CodePoint) -> Option<Self> {
64        match c.to_char_lossy() {
65            '<' => Some(Self::Left),
66            '>' => Some(Self::Right),
67            '=' => Some(Self::AfterSign),
68            '^' => Some(Self::Center),
69            _ => None,
70        }
71    }
72}
73
74impl FormatParse for FormatAlign {
75    fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
76        let mut chars = text.code_points();
77        if let Some(maybe_align) = chars.next().and_then(Self::from_char) {
78            (Some(maybe_align), chars.as_wtf8())
79        } else {
80            (None, text)
81        }
82    }
83}
84
85#[derive(Debug, Copy, Clone, PartialEq, Eq)]
86pub enum FormatSign {
87    Plus,
88    Minus,
89    MinusOrSpace,
90}
91
92impl FormatParse for FormatSign {
93    fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
94        let mut chars = text.code_points();
95        match chars.next().and_then(CodePoint::to_char) {
96            Some('-') => (Some(Self::Minus), chars.as_wtf8()),
97            Some('+') => (Some(Self::Plus), chars.as_wtf8()),
98            Some(' ') => (Some(Self::MinusOrSpace), chars.as_wtf8()),
99            _ => (None, text),
100        }
101    }
102}
103
104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
105pub enum FormatGrouping {
106    Comma,
107    Underscore,
108}
109
110impl FormatParse for FormatGrouping {
111    fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
112        let mut chars = text.code_points();
113        match chars.next().and_then(CodePoint::to_char) {
114            Some('_') => (Some(Self::Underscore), chars.as_wtf8()),
115            Some(',') => (Some(Self::Comma), chars.as_wtf8()),
116            _ => (None, text),
117        }
118    }
119}
120
121impl From<FormatGrouping> for char {
122    fn from(fg: FormatGrouping) -> Self {
123        match fg {
124            FormatGrouping::Comma => ',',
125            FormatGrouping::Underscore => '_',
126        }
127    }
128}
129
130impl From<&FormatGrouping> for char {
131    fn from(fg: &FormatGrouping) -> Self {
132        Self::from(*fg)
133    }
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, Eq)]
137pub enum FormatType {
138    String,
139    Binary,
140    Character,
141    Decimal,
142    Octal,
143    Number(Case),
144    Hex(Case),
145    Exponent(Case),
146    GeneralFormat(Case),
147    FixedPoint(Case),
148    Percentage,
149    Unknown(char),
150}
151
152impl From<&FormatType> for char {
153    fn from(from: &FormatType) -> Self {
154        match from {
155            FormatType::String => 's',
156            FormatType::Binary => 'b',
157            FormatType::Character => 'c',
158            FormatType::Decimal => 'd',
159            FormatType::Octal => 'o',
160            FormatType::Number(Case::Lower) => 'n',
161            FormatType::Number(Case::Upper) => 'N',
162            FormatType::Hex(Case::Lower) => 'x',
163            FormatType::Hex(Case::Upper) => 'X',
164            FormatType::Exponent(Case::Lower) => 'e',
165            FormatType::Exponent(Case::Upper) => 'E',
166            FormatType::GeneralFormat(Case::Lower) => 'g',
167            FormatType::GeneralFormat(Case::Upper) => 'G',
168            FormatType::FixedPoint(Case::Lower) => 'f',
169            FormatType::FixedPoint(Case::Upper) => 'F',
170            FormatType::Percentage => '%',
171            FormatType::Unknown(c) => *c,
172        }
173    }
174}
175
176impl FormatParse for FormatType {
177    fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
178        let mut chars = text.code_points();
179        match chars.next().and_then(CodePoint::to_char) {
180            Some('s') => (Some(Self::String), chars.as_wtf8()),
181            Some('b') => (Some(Self::Binary), chars.as_wtf8()),
182            Some('c') => (Some(Self::Character), chars.as_wtf8()),
183            Some('d') => (Some(Self::Decimal), chars.as_wtf8()),
184            Some('o') => (Some(Self::Octal), chars.as_wtf8()),
185            Some('n') => (Some(Self::Number(Case::Lower)), chars.as_wtf8()),
186            Some('N') => (Some(Self::Number(Case::Upper)), chars.as_wtf8()),
187            Some('x') => (Some(Self::Hex(Case::Lower)), chars.as_wtf8()),
188            Some('X') => (Some(Self::Hex(Case::Upper)), chars.as_wtf8()),
189            Some('e') => (Some(Self::Exponent(Case::Lower)), chars.as_wtf8()),
190            Some('E') => (Some(Self::Exponent(Case::Upper)), chars.as_wtf8()),
191            Some('f') => (Some(Self::FixedPoint(Case::Lower)), chars.as_wtf8()),
192            Some('F') => (Some(Self::FixedPoint(Case::Upper)), chars.as_wtf8()),
193            Some('g') => (Some(Self::GeneralFormat(Case::Lower)), chars.as_wtf8()),
194            Some('G') => (Some(Self::GeneralFormat(Case::Upper)), chars.as_wtf8()),
195            Some('%') => (Some(Self::Percentage), chars.as_wtf8()),
196            Some(c) => (Some(Self::Unknown(c)), chars.as_wtf8()),
197            _ => (None, text),
198        }
199    }
200}
201
202#[derive(Clone, Copy, Debug, PartialEq, Eq)]
203pub struct FormatSpec {
204    fill: Option<CodePoint>,
205    align: Option<FormatAlign>,
206    align_specified: bool,
207    sign: Option<FormatSign>,
208    no_neg_0: bool,
209    alternate_form: bool,
210    width: Option<usize>,
211    grouping_option: Option<FormatGrouping>,
212    precision: Option<usize>,
213    frac_grouping_option: Option<FormatGrouping>,
214    format_type: Option<FormatType>,
215}
216
217fn get_num_digits(text: &Wtf8) -> usize {
218    for (index, character) in text.code_point_indices() {
219        if !character.is_char_and(|c| c.is_ascii_digit()) {
220            return index;
221        }
222    }
223    text.len()
224}
225
226fn parse_fill_and_align(text: &Wtf8) -> (Option<CodePoint>, Option<FormatAlign>, &Wtf8) {
227    let char_indices: Vec<(usize, CodePoint)> = text.code_point_indices().take(3).collect();
228    if char_indices.is_empty() {
229        (None, None, text)
230    } else if char_indices.len() == 1 {
231        let (maybe_align, remaining) = FormatAlign::parse(text);
232        (None, maybe_align, remaining)
233    } else {
234        let (maybe_align, remaining) = FormatAlign::parse(&text[char_indices[1].0..]);
235        if maybe_align.is_some() {
236            (Some(char_indices[0].1), maybe_align, remaining)
237        } else {
238            let (only_align, only_align_remaining) = FormatAlign::parse(text);
239            (None, only_align, only_align_remaining)
240        }
241    }
242}
243
244fn parse_number(text: &Wtf8) -> Result<(Option<usize>, &Wtf8), FormatSpecError> {
245    let num_digits: usize = get_num_digits(text);
246    if num_digits == 0 {
247        return Ok((None, text));
248    }
249    if let Some(num) = parse_usize(&text[..num_digits]) {
250        Ok((Some(num), &text[num_digits..]))
251    } else {
252        // NOTE: this condition is different from CPython
253        Err(FormatSpecError::DecimalDigitsTooMany)
254    }
255}
256
257fn parse_alternate_form(text: &Wtf8) -> (bool, &Wtf8) {
258    let mut chars = text.code_points();
259    match chars.next().and_then(CodePoint::to_char) {
260        Some('#') => (true, chars.as_wtf8()),
261        _ => (false, text),
262    }
263}
264
265fn parse_no_negative_zero(text: &Wtf8) -> (bool, &Wtf8) {
266    let mut chars = text.code_points();
267    match chars.next().and_then(CodePoint::to_char) {
268        Some('z') => (true, chars.as_wtf8()),
269        _ => (false, text),
270    }
271}
272
273fn parse_zero(text: &Wtf8) -> (bool, &Wtf8) {
274    let mut chars = text.code_points();
275    match chars.next().and_then(CodePoint::to_char) {
276        Some('0') => (true, chars.as_wtf8()),
277        _ => (false, text),
278    }
279}
280
281fn parse_char(text: &Wtf8, expected: char) -> (bool, &Wtf8) {
282    let mut chars = text.code_points();
283    if chars.next().and_then(CodePoint::to_char) == Some(expected) {
284        (true, chars.as_wtf8())
285    } else {
286        (false, text)
287    }
288}
289
290fn parse_precision(
291    text: &Wtf8,
292) -> Result<(Option<usize>, Option<FormatGrouping>, &Wtf8), FormatSpecError> {
293    let (dot, text) = parse_char(text, '.');
294    if !dot {
295        return Ok((None, None, text));
296    }
297    let (precision, text) = parse_number(text)?;
298    if let Some(precision) = precision
299        && precision > i32::MAX as usize
300    {
301        return Err(FormatSpecError::PrecisionTooBig);
302    }
303    let mut frac_grouping = None;
304    let (comma, text) = parse_char(text, ',');
305    if comma {
306        frac_grouping = Some(FormatGrouping::Comma);
307    }
308    let (underscore, text) = parse_char(text, '_');
309    if underscore {
310        if frac_grouping.is_some() {
311            return Err(FormatSpecError::ExclusiveFormat(',', '_'));
312        }
313        frac_grouping = Some(FormatGrouping::Underscore);
314    }
315    let (trailing_comma, _) = parse_char(text, ',');
316    if trailing_comma && frac_grouping == Some(FormatGrouping::Underscore) {
317        return Err(FormatSpecError::ExclusiveFormat(',', '_'));
318    }
319    // Not having a precision or underscore/comma after a dot is an error.
320    if precision.is_none() && frac_grouping.is_none() {
321        return Err(FormatSpecError::PrecisionMissing);
322    }
323    Ok((precision, frac_grouping, text))
324}
325
326impl FormatSpec {
327    pub fn parse(text: impl AsRef<Wtf8>) -> Result<Self, FormatSpecError> {
328        Self::_parse(text.as_ref())
329    }
330
331    fn _parse(text: &Wtf8) -> Result<Self, FormatSpecError> {
332        let (mut fill, mut align, text) = parse_fill_and_align(text);
333        let align_specified = align.is_some();
334        let (sign, text) = FormatSign::parse(text);
335        let (no_neg_0, text) = parse_no_negative_zero(text);
336        let (alternate_form, text) = parse_alternate_form(text);
337        let (zero, text) = parse_zero(text);
338        let (width, text) = parse_number(text)?;
339        if let Some(w) = width
340            && w > i32::MAX as usize
341        {
342            return Err(FormatSpecError::DecimalDigitsTooMany);
343        }
344        let (grouping_option, text) = FormatGrouping::parse(text);
345        if let Some(grouping) = grouping_option {
346            Self::validate_separator(grouping, text)?;
347        }
348        let (precision, frac_grouping_option, text) = parse_precision(text)?;
349        let (format_type, text) = FormatType::parse(text);
350        if !text.is_empty() {
351            return Err(FormatSpecError::InvalidFormatSpecifier);
352        }
353
354        if zero && fill.is_none() {
355            fill.replace('0'.into());
356            align = align.or(Some(FormatAlign::AfterSign));
357        }
358
359        Ok(Self {
360            fill,
361            align,
362            align_specified,
363            sign,
364            no_neg_0,
365            alternate_form,
366            width,
367            grouping_option,
368            precision,
369            frac_grouping_option,
370            format_type,
371        })
372    }
373
374    fn validate_separator(grouping: FormatGrouping, text: &Wtf8) -> Result<(), FormatSpecError> {
375        let mut chars = text.code_points().peekable();
376        let grouping_char = char::from(grouping);
377        match chars.peek().and_then(|cp| CodePoint::to_char(*cp)) {
378            Some(c) if c == ',' || c == '_' => {
379                if c == grouping_char {
380                    Err(FormatSpecError::UnspecifiedFormat(c, c))
381                } else {
382                    Err(FormatSpecError::ExclusiveFormat(',', '_'))
383                }
384            }
385            _ => Ok(()),
386        }
387    }
388
389    fn compute_fill_string(fill_char: CodePoint, fill_chars_needed: i32) -> Wtf8Buf {
390        (0..fill_chars_needed).map(|_| fill_char).collect()
391    }
392
393    fn add_magnitude_separators_for_char(
394        magnitude_str: String,
395        inter: i32,
396        sep: char,
397        disp_digit_cnt: i32,
398    ) -> String {
399        // Group only the leading integer digits; the trailing remainder must
400        // never receive separators. For decimal and float output (interval 3)
401        // that remainder is the decimal point and fraction, an exponent
402        // (`e+NN`), or a trailing percent sign. Hex/octal/binary output
403        // (interval 4) has no such tail and its `a`-`f`/`e`/`E` are digits, so
404        // the whole magnitude is groupable.
405        let int_len = if inter == 4 {
406            magnitude_str.len()
407        } else {
408            magnitude_str
409                .bytes()
410                .position(|b| !b.is_ascii_digit())
411                .unwrap_or(magnitude_str.len())
412        };
413        // No leading integer digits (e.g. "inf"/"nan") means nothing to group;
414        // leave any width padding to the fill/align step.
415        if int_len == 0 {
416            return magnitude_str;
417        }
418        let magnitude_int_str = magnitude_str[..int_len].to_string();
419        let remainder = &magnitude_str[int_len..];
420        let dec_digit_cnt = magnitude_str.len() as i32 - magnitude_int_str.len() as i32;
421        let int_digit_cnt = disp_digit_cnt - dec_digit_cnt;
422        let mut result = Self::separate_integer(magnitude_int_str, inter, sep, int_digit_cnt);
423        result.push_str(remainder);
424        result
425    }
426
427    fn separate_integer(
428        magnitude_str: String,
429        inter: i32,
430        sep: char,
431        disp_digit_cnt: i32,
432    ) -> String {
433        let magnitude_len = magnitude_str.len() as i32;
434        let offset = (disp_digit_cnt % (inter + 1) == 0) as i32;
435        let disp_digit_cnt = disp_digit_cnt + offset;
436        let pad_cnt = disp_digit_cnt - magnitude_len;
437        let sep_cnt = disp_digit_cnt / (inter + 1);
438        let diff = pad_cnt - sep_cnt;
439        if pad_cnt > 0 && diff > 0 {
440            // separate with 0 padding
441            let padding = "0".repeat(diff as usize);
442            let padded_num = format!("{padding}{magnitude_str}");
443            Self::insert_separator(padded_num, inter, sep, sep_cnt)
444        } else {
445            // separate without padding
446            let sep_cnt = (magnitude_len - 1) / inter;
447            Self::insert_separator(magnitude_str, inter, sep, sep_cnt)
448        }
449    }
450
451    fn insert_separator(mut magnitude_str: String, inter: i32, sep: char, sep_cnt: i32) -> String {
452        let magnitude_len = magnitude_str.len() as i32;
453        for i in 1..=sep_cnt {
454            magnitude_str.insert((magnitude_len - inter * i) as usize, sep);
455        }
456        magnitude_str
457    }
458
459    fn validate_format(&self, default_format_type: FormatType) -> Result<(), FormatSpecError> {
460        let format_type = self.format_type.as_ref().unwrap_or(&default_format_type);
461        match (&self.grouping_option, format_type) {
462            (
463                Some(FormatGrouping::Comma),
464                FormatType::String
465                | FormatType::Character
466                | FormatType::Binary
467                | FormatType::Octal
468                | FormatType::Hex(_)
469                | FormatType::Number(_)
470                | FormatType::Unknown(_),
471            ) => {
472                let ch = char::from(format_type);
473                Err(FormatSpecError::UnspecifiedFormat(',', ch))
474            }
475            (
476                Some(FormatGrouping::Underscore),
477                FormatType::String
478                | FormatType::Character
479                | FormatType::Number(_)
480                | FormatType::Unknown(_),
481            ) => {
482                let ch = char::from(format_type);
483                Err(FormatSpecError::UnspecifiedFormat('_', ch))
484            }
485            _ => Ok(()),
486        }?;
487        if let Some(grouping) = self.frac_grouping_option
488            && matches!(format_type, FormatType::Number(_))
489        {
490            let ch = char::from(format_type);
491            return Err(FormatSpecError::UnspecifiedFormat(char::from(grouping), ch));
492        }
493        Ok(())
494    }
495
496    fn formatted_magnitude_is_zero(magnitude: &str) -> bool {
497        let mut saw_digit = false;
498        for byte in magnitude.bytes() {
499            if byte.is_ascii_digit() {
500                saw_digit = true;
501                if byte != b'0' {
502                    return false;
503                }
504            }
505        }
506        saw_digit
507    }
508
509    fn is_negative_after_zero_coercion(&self, num: f64, magnitude: &str) -> bool {
510        num.is_sign_negative()
511            && !num.is_nan()
512            && !(self.no_neg_0 && Self::formatted_magnitude_is_zero(magnitude))
513    }
514
515    fn validate_complex_padding_and_alignment(&self) -> Result<(), FormatSpecError> {
516        match &self.fill.unwrap_or_else(|| ' '.into()).to_char() {
517            Some('0') => Err(FormatSpecError::ZeroPadding),
518            _ if self.align == Some(FormatAlign::AfterSign) => Err(FormatSpecError::AlignmentFlag),
519            _ => Ok(()),
520        }
521    }
522
523    const fn get_separator_interval(&self) -> usize {
524        match self.format_type {
525            Some(FormatType::Binary | FormatType::Octal | FormatType::Hex(_)) => 4,
526            Some(
527                FormatType::Decimal
528                | FormatType::FixedPoint(_)
529                | FormatType::GeneralFormat(_)
530                | FormatType::Exponent(_)
531                | FormatType::Percentage
532                | FormatType::Number(_),
533            ) => 3,
534            None => 3,
535            _ => panic!("Separators only valid for numbers!"),
536        }
537    }
538
539    fn add_magnitude_separators(&self, magnitude_str: String, prefix: &str) -> String {
540        match &self.grouping_option {
541            Some(fg) => {
542                let sep = char::from(fg);
543                let inter = self.get_separator_interval().try_into().unwrap();
544                let magnitude_len = magnitude_str.len();
545                let disp_digit_cnt = if self.fill == Some('0'.into())
546                    && self.align == Some(FormatAlign::AfterSign)
547                {
548                    let width = self.width.unwrap_or(magnitude_len) as i32
549                        - prefix.len() as i32
550                        - self.frac_separator_count(&magnitude_str) as i32;
551                    cmp::max(width, magnitude_len as i32)
552                } else {
553                    magnitude_len as i32
554                };
555                Self::add_magnitude_separators_for_char(magnitude_str, inter, sep, disp_digit_cnt)
556            }
557            None => magnitude_str,
558        }
559    }
560
561    fn frac_digit_span(&self, magnitude_str: &str) -> Option<(FormatGrouping, usize, usize)> {
562        let grouping = self.frac_grouping_option?;
563        let start = magnitude_str.find('.')? + 1;
564        let end = magnitude_str[start..]
565            .bytes()
566            .position(|b| !b.is_ascii_digit())
567            .map_or(magnitude_str.len(), |offset| start + offset);
568        (start < end).then_some((grouping, start, end))
569    }
570
571    fn frac_separator_count(&self, magnitude_str: &str) -> usize {
572        match self.frac_digit_span(magnitude_str) {
573            Some((_, start, end)) => (end - start - 1) / self.get_separator_interval(),
574            None => 0,
575        }
576    }
577
578    fn add_frac_separators(&self, magnitude_str: String) -> String {
579        let Some((grouping, start, end)) = self.frac_digit_span(&magnitude_str) else {
580            return magnitude_str;
581        };
582        let inter = self.get_separator_interval();
583        let sep = char::from(grouping);
584        let mut result = magnitude_str[..start].to_string();
585        let mut frac = &magnitude_str[start..end];
586        while frac.len() > inter {
587            result.push_str(&frac[..inter]);
588            result.push(sep);
589            frac = &frac[inter..];
590        }
591        result.push_str(frac);
592        result.push_str(&magnitude_str[end..]);
593        result
594    }
595
596    /// Returns true if this format spec uses the locale-aware 'n' format type.
597    #[must_use]
598    pub fn has_locale_format(&self) -> bool {
599        matches!(self.format_type, Some(FormatType::Number(Case::Lower)))
600    }
601
602    /// Returns true if this format spec produces a decimal int representation
603    /// subject to `sys.get_int_max_str_digits()` (no spec, 'd', or 'n').
604    /// Binary bases ('b', 'o', 'x', 'X') are exempt per CPython. 'N' is rejected
605    /// later in `format_int` as `UnknownFormatCode`, so it is not included here.
606    #[must_use]
607    pub fn is_decimal_int_format(&self) -> bool {
608        matches!(
609            self.format_type,
610            None | Some(FormatType::Decimal | FormatType::Number(Case::Lower))
611        )
612    }
613
614    /// Insert locale-aware thousands separators into an integer string.
615    /// Follows CPython's GroupGenerator logic for variable-width grouping.
616    fn insert_locale_grouping(int_part: &str, locale: &LocaleInfo) -> String {
617        if locale.grouping.is_empty() || locale.thousands_sep.is_empty() || int_part.len() <= 1 {
618            return int_part.to_string();
619        }
620
621        let mut group_idx = 0;
622        let mut group_size = locale.grouping[0] as usize;
623
624        if group_size == 0 {
625            return int_part.to_string();
626        }
627
628        // Collect groups of digits from right to left
629        let len = int_part.len();
630        let mut groups: Vec<&str> = Vec::new();
631        let mut pos = len;
632
633        loop {
634            if pos <= group_size {
635                groups.push(&int_part[..pos]);
636                break;
637            }
638
639            groups.push(&int_part[pos - group_size..pos]);
640            pos -= group_size;
641
642            // Advance to next group size
643            if group_idx + 1 < locale.grouping.len() {
644                let next = locale.grouping[group_idx + 1] as usize;
645                if next != 0 {
646                    group_size = next;
647                    group_idx += 1;
648                }
649                // 0 means repeat previous group size forever
650            }
651        }
652
653        // Groups were collected right-to-left, reverse to get left-to-right
654        groups.reverse();
655        groups.join(&locale.thousands_sep)
656    }
657
658    /// Apply locale-aware grouping and decimal point replacement to a formatted number.
659    fn apply_locale_formatting(magnitude_str: String, locale: &LocaleInfo) -> String {
660        let mut parts = magnitude_str.splitn(2, '.');
661        let int_part = parts.next().unwrap();
662        let grouped = Self::insert_locale_grouping(int_part, locale);
663
664        if let Some(frac_part) = parts.next() {
665            format!("{grouped}{}{frac_part}", locale.decimal_point)
666        } else {
667            grouped
668        }
669    }
670
671    /// Format an integer with locale-aware 'n' format.
672    pub fn format_int_locale(
673        &self,
674        num: &BigInt,
675        locale: &LocaleInfo,
676    ) -> Result<String, FormatSpecError> {
677        self.validate_format(FormatType::Decimal)?;
678        let magnitude = num.abs();
679
680        let raw_magnitude_str = match self.format_type {
681            Some(FormatType::Number(Case::Lower)) => self.format_int_radix(magnitude, 10),
682            _ => return self.format_int(num),
683        }?;
684        if self.no_neg_0 {
685            return Err(FormatSpecError::NegativeZeroCoercionNotAllowed("integer"));
686        }
687
688        let magnitude_str = Self::apply_locale_formatting(raw_magnitude_str, locale);
689
690        let format_sign = self.sign.unwrap_or(FormatSign::Minus);
691        let sign_str = match num.sign() {
692            Sign::Minus => "-",
693            _ => match format_sign {
694                FormatSign::Plus => "+",
695                FormatSign::Minus => "",
696                FormatSign::MinusOrSpace => " ",
697            },
698        };
699
700        Ok(
701            self.format_sign_and_align(
702                &AsciiStr::new(&magnitude_str),
703                sign_str,
704                FormatAlign::Right,
705            ),
706        )
707    }
708
709    /// Format a float with locale-aware 'n' format.
710    pub fn format_float_locale(
711        &self,
712        num: f64,
713        locale: &LocaleInfo,
714    ) -> Result<String, FormatSpecError> {
715        self.validate_format(FormatType::FixedPoint(Case::Lower))?;
716        let precision = self.precision.unwrap_or(6);
717        let magnitude = num.abs();
718
719        let raw_magnitude_str = match &self.format_type {
720            Some(FormatType::Number(case)) => {
721                let precision = if precision == 0 { 1 } else { precision };
722                Ok(float::format_general(
723                    precision,
724                    magnitude,
725                    *case,
726                    self.alternate_form,
727                    false,
728                ))
729            }
730            _ => return self.format_float(num),
731        }?;
732
733        let magnitude_str = Self::apply_locale_formatting(raw_magnitude_str, locale);
734
735        let format_sign = self.sign.unwrap_or(FormatSign::Minus);
736        let sign_str = if self.is_negative_after_zero_coercion(num, &magnitude_str) {
737            "-"
738        } else {
739            match format_sign {
740                FormatSign::Plus => "+",
741                FormatSign::Minus => "",
742                FormatSign::MinusOrSpace => " ",
743            }
744        };
745
746        Ok(
747            self.format_sign_and_align(
748                &AsciiStr::new(&magnitude_str),
749                sign_str,
750                FormatAlign::Right,
751            ),
752        )
753    }
754
755    /// Format a complex number with locale-aware 'n' format.
756    pub fn format_complex_locale(
757        &self,
758        num: &Complex64,
759        locale: &LocaleInfo,
760    ) -> Result<String, FormatSpecError> {
761        self.validate_format(FormatType::FixedPoint(Case::Lower))?;
762        // Reuse format_complex_re_im with 'g' type to get the base formatted parts,
763        // then apply locale grouping. This matches CPython's format_complex_internal:
764        // 'n' → 'g', add_parens=0, skip_re=0.
765        let locale_spec = Self {
766            format_type: Some(FormatType::GeneralFormat(Case::Lower)),
767            ..*self
768        };
769        let (formatted_re, formatted_im) = locale_spec.format_complex_re_im(num)?;
770
771        // Apply locale grouping to both parts
772        let grouped_re = if formatted_re.is_empty() {
773            formatted_re
774        } else {
775            // Split sign from magnitude, apply grouping, recombine
776            let (sign, mag) = if formatted_re.starts_with('-')
777                || formatted_re.starts_with('+')
778                || formatted_re.starts_with(' ')
779            {
780                formatted_re.split_at(1)
781            } else {
782                ("", formatted_re.as_str())
783            };
784            format!(
785                "{sign}{}",
786                Self::apply_locale_formatting(mag.to_string(), locale)
787            )
788        };
789
790        // formatted_im is like "+1234j" or "-1234j" or "1234j"
791        // Split sign, magnitude, and 'j' suffix
792        let im_str = &formatted_im;
793        let (im_sign, im_rest) = if im_str.starts_with('+') || im_str.starts_with('-') {
794            im_str.split_at(1)
795        } else {
796            ("", im_str.as_str())
797        };
798        let im_mag = im_rest.strip_suffix('j').unwrap_or(im_rest);
799        let im_grouped = Self::apply_locale_formatting(im_mag.to_string(), locale);
800        let grouped_im = format!("{im_sign}{im_grouped}j");
801
802        // No parentheses for 'n' format (CPython: add_parens=0)
803        let magnitude_str = format!("{grouped_re}{grouped_im}");
804
805        self.validate_complex_padding_and_alignment()?;
806        Ok(self.format_sign_and_align(&AsciiStr::new(&magnitude_str), "", FormatAlign::Right))
807    }
808
809    /// Whether the spec carries nothing at all, which is what `format(value, "")`
810    /// parses to. Written as a destructure so a new field cannot be forgotten here.
811    fn is_empty(&self) -> bool {
812        let Self {
813            fill,
814            align,
815            align_specified,
816            sign,
817            no_neg_0,
818            alternate_form,
819            width,
820            grouping_option,
821            precision,
822            frac_grouping_option,
823            format_type,
824        } = self;
825        fill.is_none()
826            && align.is_none()
827            && !align_specified
828            && sign.is_none()
829            && !no_neg_0
830            && !alternate_form
831            && width.is_none()
832            && grouping_option.is_none()
833            && precision.is_none()
834            && frac_grouping_option.is_none()
835            && format_type.is_none()
836    }
837
838    pub fn format_bool(&self, input: bool) -> Result<String, FormatSpecError> {
839        self.validate_format(FormatType::Decimal)?;
840        let x = u8::from(input);
841        match &self.format_type {
842            Some(
843                FormatType::Binary
844                | FormatType::Decimal
845                | FormatType::Octal
846                | FormatType::Number(Case::Lower)
847                | FormatType::Hex(_)
848                | FormatType::GeneralFormat(_)
849                | FormatType::Character,
850            ) => self.format_int(&BigInt::from_u8(x).unwrap()),
851            Some(FormatType::Exponent(_) | FormatType::FixedPoint(_) | FormatType::Percentage) => {
852                self.format_float(x as f64)
853            }
854            // Only the empty spec spells the value out. Everything else without a
855            // presentation type, a width or an alignment included, formats `bool`
856            // the way it formats the `int` it is.
857            None if self.is_empty() => Ok(if input { "True" } else { "False" }.to_owned()),
858            None => self.format_int(&BigInt::from_u8(x).unwrap()),
859            Some(format_type) => {
860                let ch = char::from(format_type);
861                Err(FormatSpecError::UnknownFormatCode(ch, "bool"))
862            }
863        }
864    }
865
866    pub fn format_float(&self, num: f64) -> Result<String, FormatSpecError> {
867        self.validate_format(FormatType::FixedPoint(Case::Lower))?;
868        let precision = self.precision.unwrap_or(6);
869        let magnitude = num.abs();
870        let raw_magnitude_str: Result<String, FormatSpecError> = match &self.format_type {
871            Some(FormatType::FixedPoint(case)) => Ok(float::format_fixed(
872                precision,
873                magnitude,
874                *case,
875                self.alternate_form,
876            )),
877            Some(
878                FormatType::Decimal
879                | FormatType::Binary
880                | FormatType::Octal
881                | FormatType::Hex(_)
882                | FormatType::String
883                | FormatType::Character
884                | FormatType::Number(Case::Upper)
885                | FormatType::Unknown(_),
886            ) => {
887                let ch = char::from(self.format_type.as_ref().unwrap());
888                Err(FormatSpecError::UnknownFormatCode(ch, "float"))
889            }
890            Some(FormatType::GeneralFormat(case) | FormatType::Number(case)) => {
891                let precision = if precision == 0 { 1 } else { precision };
892                Ok(float::format_general(
893                    precision,
894                    magnitude,
895                    *case,
896                    self.alternate_form,
897                    false,
898                ))
899            }
900            Some(FormatType::Exponent(case)) => Ok(float::format_exponent(
901                precision,
902                magnitude,
903                *case,
904                self.alternate_form,
905            )),
906            Some(FormatType::Percentage) => match magnitude {
907                magnitude if magnitude.is_nan() => Ok("nan%".to_owned()),
908                magnitude if magnitude.is_infinite() => Ok("inf%".to_owned()),
909                _ => {
910                    let scaled = magnitude * 100.0;
911                    // `magnitude * 100` can overflow a finite input to +inf
912                    // (e.g. f64::MAX). Emit "inf%" so the outer sign handler
913                    // produces "-inf%" or "inf%" consistently with CPython.
914                    if scaled.is_infinite() {
915                        Ok("inf%".to_owned())
916                    } else {
917                        let capped = float::clamp_fmt_precision(precision);
918                        let mut result = format!("{scaled:.capped$}");
919                        // Pad with '0's up to the requested precision to match
920                        // CPython byte-identically past the internal cap.
921                        let missing = precision.saturating_sub(capped);
922                        if missing > 0 {
923                            result.extend(core::iter::repeat_n('0', missing));
924                        }
925                        let point = float::decimal_point_or_empty(precision, self.alternate_form);
926                        Ok(format!("{result}{point}%"))
927                    }
928                }
929            },
930            None => match magnitude {
931                magnitude if magnitude.is_nan() => Ok("nan".to_owned()),
932                magnitude if magnitude.is_infinite() => Ok("inf".to_owned()),
933                _ => match self.precision {
934                    Some(precision) => {
935                        // Empty presentation type with a precision behaves like
936                        // `g` but repr-like: precision is clamped to at least 1,
937                        // and an integer-looking result keeps a trailing `.0`
938                        // (Py_DTSF_ADD_DOT_0).
939                        let precision = if precision == 0 { 1 } else { precision };
940                        let s = float::format_general(
941                            precision,
942                            magnitude,
943                            Case::Lower,
944                            self.alternate_form,
945                            true,
946                        );
947                        Ok(if s.bytes().any(|b| matches!(b, b'.' | b'e' | b'E')) {
948                            s
949                        } else {
950                            format!("{s}.0")
951                        })
952                    }
953                    None => {
954                        let s = float::to_string(magnitude);
955                        // Alternate form forces a decimal point into the
956                        // repr-like output. Only exponent-form values lack one
957                        // (`1e+16` -> `1.e+16`); fixed-form repr already carries
958                        // a `.`.
959                        Ok(if self.alternate_form && !s.contains('.') {
960                            match s.find(['e', 'E']) {
961                                Some(pos) => format!("{}.{}", &s[..pos], &s[pos..]),
962                                None => format!("{s}."),
963                            }
964                        } else {
965                            s
966                        })
967                    }
968                },
969            },
970        };
971        let raw_magnitude_str = raw_magnitude_str?;
972        let format_sign = self.sign.unwrap_or(FormatSign::Minus);
973        let sign_str = if self.is_negative_after_zero_coercion(num, &raw_magnitude_str) {
974            "-"
975        } else {
976            match format_sign {
977                FormatSign::Plus => "+",
978                FormatSign::Minus => "",
979                FormatSign::MinusOrSpace => " ",
980            }
981        };
982        let magnitude_str = self.add_magnitude_separators(raw_magnitude_str, sign_str);
983        let magnitude_str = self.add_frac_separators(magnitude_str);
984        Ok(
985            self.format_sign_and_align(
986                &AsciiStr::new(&magnitude_str),
987                sign_str,
988                FormatAlign::Right,
989            ),
990        )
991    }
992
993    #[inline]
994    fn format_int_radix(&self, magnitude: BigInt, radix: u32) -> Result<String, FormatSpecError> {
995        match self.precision {
996            Some(_) => Err(FormatSpecError::PrecisionNotAllowed),
997            None => Ok(magnitude.to_str_radix(radix)),
998        }
999    }
1000
1001    pub fn format_int(&self, num: &BigInt) -> Result<String, FormatSpecError> {
1002        self.validate_format(FormatType::Decimal)?;
1003        let magnitude = num.abs();
1004        let prefix = if self.alternate_form {
1005            match self.format_type {
1006                Some(FormatType::Binary) => "0b",
1007                Some(FormatType::Octal) => "0o",
1008                Some(FormatType::Hex(Case::Lower)) => "0x",
1009                Some(FormatType::Hex(Case::Upper)) => "0X",
1010                _ => "",
1011            }
1012        } else {
1013            ""
1014        };
1015        let raw_magnitude_str = match self.format_type {
1016            Some(FormatType::Binary) => self.format_int_radix(magnitude, 2),
1017            Some(FormatType::Decimal) => self.format_int_radix(magnitude, 10),
1018            Some(FormatType::Octal) => self.format_int_radix(magnitude, 8),
1019            Some(FormatType::Hex(Case::Lower)) => self.format_int_radix(magnitude, 16),
1020            Some(FormatType::Hex(Case::Upper)) => match self.precision {
1021                Some(_) => Err(FormatSpecError::PrecisionNotAllowed),
1022                None => {
1023                    let mut result = magnitude.to_str_radix(16);
1024                    result.make_ascii_uppercase();
1025                    Ok(result)
1026                }
1027            },
1028            Some(FormatType::Number(Case::Lower)) => self.format_int_radix(magnitude, 10),
1029            Some(FormatType::Number(Case::Upper)) => {
1030                Err(FormatSpecError::UnknownFormatCode('N', "int"))
1031            }
1032            Some(FormatType::String) => Err(FormatSpecError::UnknownFormatCode('s', "int")),
1033            Some(FormatType::Character) => {
1034                if self.precision.is_some() {
1035                    Err(FormatSpecError::PrecisionNotAllowed)
1036                } else if self.no_neg_0 {
1037                    Err(FormatSpecError::NegativeZeroCoercionNotAllowed("integer"))
1038                } else {
1039                    match (self.sign, self.alternate_form) {
1040                        (Some(_), _) => Err(FormatSpecError::NotAllowed("Sign")),
1041                        (_, true) => Err(FormatSpecError::NotAllowed("Alternate form (#)")),
1042                        _ => match num
1043                            .to_i64()
1044                            .filter(|code| core::ffi::c_long::try_from(*code).is_ok())
1045                        {
1046                            None => Err(FormatSpecError::IntTooLargeForCLong),
1047                            Some(n @ 0..=0x10ffff) => {
1048                                let ch = core::char::from_u32(n as u32).unwrap().to_string();
1049                                return Ok(self.format_sign_and_align(&ch, "", FormatAlign::Right));
1050                            }
1051                            Some(_) => Err(FormatSpecError::CodeNotInRange),
1052                        },
1053                    }
1054                }
1055            }
1056            Some(
1057                FormatType::GeneralFormat(_)
1058                | FormatType::FixedPoint(_)
1059                | FormatType::Exponent(_)
1060                | FormatType::Percentage,
1061            ) => match num.to_f64() {
1062                Some(float) => return self.format_float(float),
1063                _ => Err(FormatSpecError::UnableToConvert),
1064            },
1065            Some(FormatType::Unknown(c)) => Err(FormatSpecError::UnknownFormatCode(c, "int")),
1066            None => self.format_int_radix(magnitude, 10),
1067        }?;
1068        if self.no_neg_0 {
1069            return Err(FormatSpecError::NegativeZeroCoercionNotAllowed("integer"));
1070        }
1071        let format_sign = self.sign.unwrap_or(FormatSign::Minus);
1072        let sign_str = match num.sign() {
1073            Sign::Minus => "-",
1074            _ => match format_sign {
1075                FormatSign::Plus => "+",
1076                FormatSign::Minus => "",
1077                FormatSign::MinusOrSpace => " ",
1078            },
1079        };
1080        let sign_prefix = format!("{sign_str}{prefix}");
1081        let magnitude_str = self.add_magnitude_separators(raw_magnitude_str, &sign_prefix);
1082        Ok(self.format_sign_and_align(
1083            &AsciiStr::new(&magnitude_str),
1084            &sign_prefix,
1085            FormatAlign::Right,
1086        ))
1087    }
1088
1089    pub fn format_string<T>(&self, s: &T) -> Result<String, FormatSpecError>
1090    where
1091        T: CharLen + Deref<Target = str>,
1092    {
1093        self.validate_format(FormatType::String)?;
1094        match self.format_type {
1095            Some(FormatType::String) | None => {
1096                // CPython rejects these four in this order: sign, z, #, then '='.
1097                if let Some(sign) = self.sign {
1098                    return Err(FormatSpecError::StringSpecNotAllowed(match sign {
1099                        FormatSign::MinusOrSpace => "Space",
1100                        FormatSign::Plus | FormatSign::Minus => "Sign",
1101                    }));
1102                }
1103                if self.no_neg_0 {
1104                    return Err(FormatSpecError::NegativeZeroCoercionNotAllowed("string"));
1105                }
1106                if self.alternate_form {
1107                    return Err(FormatSpecError::StringSpecNotAllowed("Alternate form (#)"));
1108                }
1109                if self.align == Some(FormatAlign::AfterSign) && self.align_specified {
1110                    return Err(FormatSpecError::StringAlignmentFlag);
1111                }
1112                // CPython parity: precision truncates BEFORE width pads.
1113                // `'{:3.2s}'.format('abc')` -> 'ab ' (truncate to 'ab', pad to 3).
1114                let truncated: String = match self.precision {
1115                    Some(p) => s.deref().chars().take(p).collect(),
1116                    None => s.deref().to_owned(),
1117                };
1118                let spec = Self {
1119                    align: if self.align == Some(FormatAlign::AfterSign) {
1120                        Some(FormatAlign::Left)
1121                    } else {
1122                        self.align
1123                    },
1124                    ..*self
1125                };
1126                Ok(spec.format_sign_and_align(&truncated, "", FormatAlign::Left))
1127            }
1128            _ => {
1129                let ch = char::from(self.format_type.as_ref().unwrap());
1130                Err(FormatSpecError::UnknownFormatCode(ch, "str"))
1131            }
1132        }
1133    }
1134
1135    pub fn format_complex(&self, num: &Complex64) -> Result<String, FormatSpecError> {
1136        let (formatted_re, formatted_im) = self.format_complex_re_im(num)?;
1137        // Enclose in parentheses if there is no format type and formatted_re is not empty
1138        let magnitude_str = if self.format_type.is_none() && !formatted_re.is_empty() {
1139            format!("({formatted_re}{formatted_im})")
1140        } else {
1141            format!("{formatted_re}{formatted_im}")
1142        };
1143        self.validate_complex_padding_and_alignment()?;
1144        Ok(self.format_sign_and_align(&AsciiStr::new(&magnitude_str), "", FormatAlign::Right))
1145    }
1146
1147    fn format_complex_re_im(&self, num: &Complex64) -> Result<(String, String), FormatSpecError> {
1148        // Format real part
1149        let formatted_re =
1150            if num.re != 0.0 || num.re.is_negative_zero() || self.format_type.is_some() {
1151                let re = self.format_complex_float(num.re)?;
1152                let sign_re = if self.is_negative_after_zero_coercion(num.re, &re) {
1153                    "-"
1154                } else {
1155                    match self.sign.unwrap_or(FormatSign::Minus) {
1156                        FormatSign::Plus => "+",
1157                        FormatSign::Minus => "",
1158                        FormatSign::MinusOrSpace => " ",
1159                    }
1160                };
1161                format!("{sign_re}{re}")
1162            } else {
1163                String::new()
1164            };
1165
1166        // Format imaginary part
1167        let im = self.format_complex_float(num.im)?;
1168        let sign_im = if self.is_negative_after_zero_coercion(num.im, &im) {
1169            "-"
1170        } else if formatted_re.is_empty() {
1171            match self.sign.unwrap_or(FormatSign::Minus) {
1172                FormatSign::Plus => "+",
1173                FormatSign::Minus => "",
1174                FormatSign::MinusOrSpace => " ",
1175            }
1176        } else {
1177            "+"
1178        };
1179        Ok((formatted_re, format!("{sign_im}{im}j")))
1180    }
1181
1182    fn format_complex_float(&self, num: f64) -> Result<String, FormatSpecError> {
1183        self.validate_format(FormatType::FixedPoint(Case::Lower))?;
1184        let precision = self.precision.unwrap_or(6);
1185        let magnitude = num.abs();
1186        // A zero precision means one significant digit, and a part of a
1187        // complex keeps no fraction of its own the way a bare float does.
1188        let general = |case| {
1189            float::format_general(
1190                if precision == 0 { 1 } else { precision },
1191                magnitude,
1192                case,
1193                self.alternate_form,
1194                false,
1195            )
1196        };
1197        let magnitude_str = match &self.format_type {
1198            Some(
1199                FormatType::Decimal
1200                | FormatType::Binary
1201                | FormatType::Octal
1202                | FormatType::Hex(_)
1203                | FormatType::String
1204                | FormatType::Character
1205                | FormatType::Number(Case::Upper)
1206                | FormatType::Percentage
1207                | FormatType::Unknown(_),
1208            ) => {
1209                let ch = char::from(self.format_type.as_ref().unwrap());
1210                Err(FormatSpecError::UnknownFormatCode(ch, "complex"))
1211            }
1212            Some(FormatType::FixedPoint(case)) => Ok(float::format_fixed(
1213                precision,
1214                magnitude,
1215                *case,
1216                self.alternate_form,
1217            )),
1218            Some(FormatType::GeneralFormat(case) | FormatType::Number(case)) => Ok(general(*case)),
1219            Some(FormatType::Exponent(case)) => Ok(float::format_exponent(
1220                precision,
1221                magnitude,
1222                *case,
1223                self.alternate_form,
1224            )),
1225            None => match magnitude {
1226                magnitude if magnitude.is_nan() => Ok("nan".to_owned()),
1227                magnitude if magnitude.is_infinite() => Ok("inf".to_owned()),
1228                _ if self.precision.is_some() => Ok(general(Case::Lower)),
1229                magnitude if magnitude.fract() == 0.0 => Ok(magnitude.trunc().to_string()),
1230                magnitude => Ok(magnitude.to_string()),
1231            },
1232        }?;
1233        let magnitude_str = match &self.grouping_option {
1234            Some(fg) => {
1235                let sep = char::from(fg);
1236                let inter = self.get_separator_interval().try_into().unwrap();
1237                let len = magnitude_str.len() as i32;
1238                Self::add_magnitude_separators_for_char(magnitude_str, inter, sep, len)
1239            }
1240            None => magnitude_str,
1241        };
1242        Ok(self.add_frac_separators(magnitude_str))
1243    }
1244
1245    fn format_sign_and_align<T>(
1246        &self,
1247        magnitude_str: &T,
1248        sign_str: &str,
1249        default_align: FormatAlign,
1250    ) -> String
1251    where
1252        T: CharLen + Deref<Target = str>,
1253    {
1254        let align = self.align.unwrap_or(default_align);
1255
1256        let num_chars = magnitude_str.char_len();
1257        let fill_char = self.fill.unwrap_or_else(|| ' '.into());
1258        let fill_chars_needed: i32 = self.width.map_or(0, |w| {
1259            cmp::max(0, (w as i32) - (num_chars as i32) - (sign_str.len() as i32))
1260        });
1261
1262        let magnitude_str = &**magnitude_str;
1263        match align {
1264            FormatAlign::Left => format!(
1265                "{}{}{}",
1266                sign_str,
1267                magnitude_str,
1268                Self::compute_fill_string(fill_char, fill_chars_needed)
1269            ),
1270            FormatAlign::Right => format!(
1271                "{}{}{}",
1272                Self::compute_fill_string(fill_char, fill_chars_needed),
1273                sign_str,
1274                magnitude_str
1275            ),
1276            FormatAlign::AfterSign => format!(
1277                "{}{}{}",
1278                sign_str,
1279                Self::compute_fill_string(fill_char, fill_chars_needed),
1280                magnitude_str
1281            ),
1282            FormatAlign::Center => {
1283                let left_fill_chars_needed = fill_chars_needed / 2;
1284                let right_fill_chars_needed = fill_chars_needed - left_fill_chars_needed;
1285                let left_fill_string = Self::compute_fill_string(fill_char, left_fill_chars_needed);
1286                let right_fill_string =
1287                    Self::compute_fill_string(fill_char, right_fill_chars_needed);
1288                format!("{left_fill_string}{sign_str}{magnitude_str}{right_fill_string}")
1289            }
1290        }
1291    }
1292}
1293
1294pub trait CharLen {
1295    /// Returns the number of characters in the text
1296    fn char_len(&self) -> usize;
1297}
1298
1299struct AsciiStr<'a> {
1300    inner: &'a str,
1301}
1302
1303impl<'a> AsciiStr<'a> {
1304    const fn new(inner: &'a str) -> Self {
1305        Self { inner }
1306    }
1307}
1308
1309impl CharLen for AsciiStr<'_> {
1310    fn char_len(&self) -> usize {
1311        self.inner.len()
1312    }
1313}
1314
1315impl CharLen for String {
1316    fn char_len(&self) -> usize {
1317        self.chars().count()
1318    }
1319}
1320
1321impl Deref for AsciiStr<'_> {
1322    type Target = str;
1323
1324    fn deref(&self) -> &Self::Target {
1325        self.inner
1326    }
1327}
1328
1329#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1330pub enum FormatSpecError {
1331    DecimalDigitsTooMany,
1332    PrecisionTooBig,
1333    PrecisionMissing,
1334    InvalidFormatSpecifier,
1335    UnspecifiedFormat(char, char),
1336    ExclusiveFormat(char, char),
1337    UnknownFormatCode(char, &'static str),
1338    PrecisionNotAllowed,
1339    NotAllowed(&'static str),
1340    UnableToConvert,
1341    CodeNotInRange,
1342    IntTooLargeForCLong,
1343    ZeroPadding,
1344    AlignmentFlag,
1345    NegativeZeroCoercionNotAllowed(&'static str),
1346    StringAlignmentFlag,
1347    StringSpecNotAllowed(&'static str),
1348    NotImplemented(char, &'static str),
1349}
1350
1351#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1352pub enum FormatParseError {
1353    UnmatchedBracket,
1354    MissingStartBracket,
1355    UnescapedStartBracketInLiteral,
1356    InvalidFormatSpecifier,
1357    UnknownConversion,
1358    EmptyAttribute,
1359    MissingRightBracket,
1360    InvalidCharacterAfterRightBracket,
1361    TooManyDecimalDigits,
1362}
1363
1364impl FromStr for FormatSpec {
1365    type Err = FormatSpecError;
1366    fn from_str(s: &str) -> Result<Self, Self::Err> {
1367        Self::parse(s)
1368    }
1369}
1370
1371#[derive(Debug, PartialEq, Eq)]
1372pub enum FieldNamePart {
1373    Attribute(Wtf8Buf),
1374    Index(usize),
1375    StringIndex(Wtf8Buf),
1376}
1377
1378impl FieldNamePart {
1379    fn parse_part(
1380        chars: &mut impl PeekingNext<Item = CodePoint>,
1381    ) -> Result<Option<Self>, FormatParseError> {
1382        chars
1383            .next()
1384            .map(|ch| match ch.to_char_lossy() {
1385                '.' => {
1386                    let mut attribute = Wtf8Buf::new();
1387                    for ch in chars.peeking_take_while(|ch| *ch != '.' && *ch != '[') {
1388                        attribute.push(ch);
1389                    }
1390                    if attribute.is_empty() {
1391                        Err(FormatParseError::EmptyAttribute)
1392                    } else {
1393                        Ok(Self::Attribute(attribute))
1394                    }
1395                }
1396                '[' => {
1397                    let mut index = Wtf8Buf::new();
1398                    for ch in chars {
1399                        if ch == ']' {
1400                            return if index.is_empty() {
1401                                Err(FormatParseError::EmptyAttribute)
1402                            } else if let Some(index) = parse_usize(&index) {
1403                                Ok(Self::Index(index))
1404                            } else {
1405                                Ok(Self::StringIndex(index))
1406                            };
1407                        }
1408                        index.push(ch);
1409                    }
1410                    Err(FormatParseError::MissingRightBracket)
1411                }
1412                _ => Err(FormatParseError::InvalidCharacterAfterRightBracket),
1413            })
1414            .transpose()
1415    }
1416}
1417
1418#[derive(Debug, PartialEq, Eq)]
1419pub enum FieldType {
1420    Auto,
1421    Index(usize),
1422    Keyword(Wtf8Buf),
1423}
1424
1425#[derive(Debug, PartialEq, Eq)]
1426pub struct FieldName {
1427    pub field_type: FieldType,
1428    pub parts: Vec<FieldNamePart>,
1429}
1430
1431fn parse_usize(s: &Wtf8) -> Option<usize> {
1432    s.as_str().ok().and_then(|s| s.parse().ok())
1433}
1434
1435impl FieldName {
1436    pub fn parse(text: &Wtf8) -> Result<Self, FormatParseError> {
1437        let mut chars = text.code_points().peekable();
1438        let first: Wtf8Buf = chars
1439            .peeking_take_while(|ch| *ch != '.' && *ch != '[')
1440            .collect();
1441
1442        let field_type = if first.is_empty() {
1443            FieldType::Auto
1444        } else if let Some(index) = parse_usize(&first) {
1445            // Match CPython's get_integer: digits-only segment must fit in
1446            // Py_ssize_t. Above that, raise ValueError at parse time.
1447            if index > isize::MAX as usize {
1448                return Err(FormatParseError::TooManyDecimalDigits);
1449            }
1450            FieldType::Index(index)
1451        } else if first
1452            .as_str()
1453            .is_ok_and(|s| s.bytes().all(|b| b.is_ascii_digit()))
1454        {
1455            // All-digit segment whose value overflows usize itself.
1456            return Err(FormatParseError::TooManyDecimalDigits);
1457        } else {
1458            FieldType::Keyword(first)
1459        };
1460
1461        let mut parts = Vec::new();
1462        while let Some(part) = FieldNamePart::parse_part(&mut chars)? {
1463            parts.push(part)
1464        }
1465
1466        Ok(Self { field_type, parts })
1467    }
1468}
1469
1470#[derive(Debug, PartialEq, Eq)]
1471pub enum FormatPart {
1472    Field {
1473        field_name: Wtf8Buf,
1474        conversion_spec: Option<CodePoint>,
1475        format_spec: Wtf8Buf,
1476    },
1477    Literal(Wtf8Buf),
1478}
1479
1480#[derive(Debug, PartialEq, Eq)]
1481pub struct FormatString {
1482    pub format_parts: Vec<FormatPart>,
1483}
1484
1485impl FormatString {
1486    fn parse_literal_single(text: &Wtf8) -> Result<(CodePoint, &Wtf8), FormatParseError> {
1487        let mut chars = text.code_points();
1488        // This should never be called with an empty str
1489        let first_char = chars.next().unwrap();
1490        // isn't this detectable only with bytes operation?
1491        if first_char == '{' || first_char == '}' {
1492            let maybe_next_char = chars.next();
1493            // if we see a bracket, it has to be escaped by doubling up to be in a literal
1494            return if maybe_next_char.is_none() || maybe_next_char.unwrap() != first_char {
1495                Err(FormatParseError::UnescapedStartBracketInLiteral)
1496            } else {
1497                Ok((first_char, chars.as_wtf8()))
1498            };
1499        }
1500        Ok((first_char, chars.as_wtf8()))
1501    }
1502
1503    fn parse_literal(text: &Wtf8) -> Result<(FormatPart, &Wtf8), FormatParseError> {
1504        let mut cur_text = text;
1505        let mut result_string = Wtf8Buf::new();
1506        while !cur_text.is_empty() {
1507            match Self::parse_literal_single(cur_text) {
1508                Ok((next_char, remaining)) => {
1509                    result_string.push(next_char);
1510                    cur_text = remaining;
1511                }
1512                Err(err) => {
1513                    return if !result_string.is_empty() {
1514                        Ok((FormatPart::Literal(result_string), cur_text))
1515                    } else {
1516                        Err(err)
1517                    };
1518                }
1519            }
1520        }
1521        Ok((FormatPart::Literal(result_string), "".as_ref()))
1522    }
1523
1524    fn parse_part_in_brackets(text: &Wtf8) -> Result<FormatPart, FormatParseError> {
1525        let mut chars = text.code_points().peekable();
1526
1527        let mut left = Wtf8Buf::new();
1528        let mut right = Wtf8Buf::new();
1529
1530        let mut split = false;
1531        let mut selected = &mut left;
1532        let mut inside_brackets = false;
1533
1534        while let Some(char) = chars.next() {
1535            if char == '[' {
1536                inside_brackets = true;
1537
1538                selected.push(char);
1539
1540                while let Some(next_char) = chars.next() {
1541                    selected.push(next_char);
1542
1543                    if next_char == ']' {
1544                        inside_brackets = false;
1545                        break;
1546                    }
1547                    if chars.peek().is_none() {
1548                        return Err(FormatParseError::MissingRightBracket);
1549                    }
1550                }
1551            } else if char == ':' && !split && !inside_brackets {
1552                split = true;
1553                selected = &mut right;
1554            } else {
1555                selected.push(char);
1556            }
1557        }
1558
1559        // before the comma is a keyword or arg index, after the comma is maybe a spec.
1560        let arg_part: &Wtf8 = &left;
1561
1562        let format_spec = if split { right } else { Wtf8Buf::new() };
1563
1564        // left can still be the conversion (!r, !s, !a)
1565        let parts: Vec<&Wtf8> = arg_part.splitn(2, "!".as_ref()).collect();
1566        // before the bang is a keyword or arg index, after the comma is maybe a conversion spec.
1567        let arg_part = parts[0];
1568
1569        let conversion_spec = parts
1570            .get(1)
1571            .map(|conversion| {
1572                // conversions are only every one character
1573                conversion
1574                    .code_points()
1575                    .exactly_one()
1576                    .map_err(|_| FormatParseError::UnknownConversion)
1577            })
1578            .transpose()?;
1579
1580        Ok(FormatPart::Field {
1581            field_name: arg_part.to_owned(),
1582            conversion_spec,
1583            format_spec,
1584        })
1585    }
1586
1587    fn parse_spec(text: &Wtf8) -> Result<(FormatPart, &Wtf8), FormatParseError> {
1588        let mut nested = false;
1589        let mut end_bracket_pos = None;
1590        let mut left = Wtf8Buf::new();
1591
1592        // There may be one layer nesting brackets in spec
1593        for (idx, c) in text.code_point_indices() {
1594            if idx == 0 {
1595                if c != '{' {
1596                    return Err(FormatParseError::MissingStartBracket);
1597                }
1598            } else if c == '{' {
1599                if nested {
1600                    return Err(FormatParseError::InvalidFormatSpecifier);
1601                }
1602                nested = true;
1603                left.push(c);
1604                continue;
1605            } else if c == '}' {
1606                if nested {
1607                    nested = false;
1608                    left.push(c);
1609                    continue;
1610                }
1611                end_bracket_pos = Some(idx);
1612                break;
1613            } else {
1614                left.push(c);
1615            }
1616        }
1617        if let Some(pos) = end_bracket_pos {
1618            let right = &text[pos..];
1619            let format_part = Self::parse_part_in_brackets(&left)?;
1620            Ok((format_part, &right[1..]))
1621        } else if text.len() == 1 {
1622            // Nothing follows the brace, so it is a stray one rather than a field
1623            // that was left open: CPython separates "{" and "a{" from "{0" and
1624            // "a{b" the same way. `{` is one byte, so a length of one is the brace
1625            // on its own.
1626            Err(FormatParseError::UnescapedStartBracketInLiteral)
1627        } else {
1628            Err(FormatParseError::UnmatchedBracket)
1629        }
1630    }
1631}
1632
1633pub trait FromTemplate<'a>: Sized {
1634    type Err;
1635    fn from_str(s: &'a Wtf8) -> Result<Self, Self::Err>;
1636}
1637
1638impl<'a> FromTemplate<'a> for FormatString {
1639    type Err = FormatParseError;
1640
1641    fn from_str(text: &'a Wtf8) -> Result<Self, Self::Err> {
1642        let mut cur_text: &Wtf8 = text;
1643        let mut parts: Vec<FormatPart> = Vec::new();
1644        while !cur_text.is_empty() {
1645            // Try to parse both literals and bracketed format parts until we
1646            // run out of text
1647            cur_text = Self::parse_literal(cur_text)
1648                .or_else(|_| Self::parse_spec(cur_text))
1649                .map(|(part, new_text)| {
1650                    parts.push(part);
1651                    new_text
1652                })?;
1653        }
1654        Ok(Self {
1655            format_parts: parts,
1656        })
1657    }
1658}
1659
1660#[cfg(test)]
1661mod tests {
1662    use super::*;
1663
1664    #[test]
1665    fn fill_and_align() {
1666        let parse_fill_and_align = |text| {
1667            let (fill, align, rest) = parse_fill_and_align(str::as_ref(text));
1668            (
1669                fill.and_then(CodePoint::to_char),
1670                align,
1671                rest.as_str().unwrap(),
1672            )
1673        };
1674        assert_eq!(
1675            parse_fill_and_align(" <"),
1676            (Some(' '), Some(FormatAlign::Left), "")
1677        );
1678        assert_eq!(
1679            parse_fill_and_align(" <22"),
1680            (Some(' '), Some(FormatAlign::Left), "22")
1681        );
1682        assert_eq!(
1683            parse_fill_and_align("<22"),
1684            (None, Some(FormatAlign::Left), "22")
1685        );
1686        assert_eq!(
1687            parse_fill_and_align(" ^^"),
1688            (Some(' '), Some(FormatAlign::Center), "^")
1689        );
1690        assert_eq!(
1691            parse_fill_and_align("==="),
1692            (Some('='), Some(FormatAlign::AfterSign), "=")
1693        );
1694    }
1695
1696    #[test]
1697    fn width_only() {
1698        let expected = Ok(FormatSpec {
1699            fill: None,
1700            align: None,
1701            align_specified: false,
1702            sign: None,
1703            no_neg_0: false,
1704            alternate_form: false,
1705            width: Some(33),
1706            grouping_option: None,
1707            precision: None,
1708            frac_grouping_option: None,
1709            format_type: None,
1710        });
1711        assert_eq!(FormatSpec::parse("33"), expected);
1712    }
1713
1714    #[test]
1715    fn fill_and_width() {
1716        let expected = Ok(FormatSpec {
1717            fill: Some('<'.into()),
1718            align: Some(FormatAlign::Right),
1719            align_specified: true,
1720            sign: None,
1721            no_neg_0: false,
1722            alternate_form: false,
1723            width: Some(33),
1724            grouping_option: None,
1725            precision: None,
1726            frac_grouping_option: None,
1727            format_type: None,
1728        });
1729        assert_eq!(FormatSpec::parse("<>33"), expected);
1730    }
1731
1732    #[test]
1733    fn all() {
1734        let expected = Ok(FormatSpec {
1735            fill: Some('<'.into()),
1736            align: Some(FormatAlign::Right),
1737            align_specified: true,
1738            sign: Some(FormatSign::Minus),
1739            no_neg_0: false,
1740            alternate_form: true,
1741            width: Some(23),
1742            grouping_option: Some(FormatGrouping::Comma),
1743            precision: Some(11),
1744            frac_grouping_option: None,
1745            format_type: Some(FormatType::Binary),
1746        });
1747        assert_eq!(FormatSpec::parse("<>-#23,.11b"), expected);
1748    }
1749
1750    fn format_bool(text: &str, value: bool) -> Result<String, FormatSpecError> {
1751        FormatSpec::parse(text).and_then(|spec| spec.format_bool(value))
1752    }
1753
1754    #[test]
1755    fn format_bool_basic() {
1756        assert_eq!(format_bool("b", true), Ok("1".to_owned()));
1757        assert_eq!(format_bool("b", false), Ok("0".to_owned()));
1758        assert_eq!(format_bool("d", true), Ok("1".to_owned()));
1759        assert_eq!(format_bool("d", false), Ok("0".to_owned()));
1760        assert_eq!(format_bool("o", true), Ok("1".to_owned()));
1761        assert_eq!(format_bool("o", false), Ok("0".to_owned()));
1762        assert_eq!(format_bool("n", true), Ok("1".to_owned()));
1763        assert_eq!(format_bool("n", false), Ok("0".to_owned()));
1764        assert_eq!(format_bool("x", true), Ok("1".to_owned()));
1765        assert_eq!(format_bool("x", false), Ok("0".to_owned()));
1766        assert_eq!(format_bool("X", true), Ok("1".to_owned()));
1767        assert_eq!(format_bool("X", false), Ok("0".to_owned()));
1768        assert_eq!(format_bool("g", true), Ok("1".to_owned()));
1769        assert_eq!(format_bool("g", false), Ok("0".to_owned()));
1770        assert_eq!(format_bool("G", true), Ok("1".to_owned()));
1771        assert_eq!(format_bool("G", false), Ok("0".to_owned()));
1772        assert_eq!(format_bool("c", true), Ok("\x01".to_owned()));
1773        assert_eq!(format_bool("c", false), Ok("\x00".to_owned()));
1774        assert_eq!(format_bool("e", true), Ok("1.000000e+00".to_owned()));
1775        assert_eq!(format_bool("e", false), Ok("0.000000e+00".to_owned()));
1776        assert_eq!(format_bool("E", true), Ok("1.000000E+00".to_owned()));
1777        assert_eq!(format_bool("E", false), Ok("0.000000E+00".to_owned()));
1778        assert_eq!(format_bool("f", true), Ok("1.000000".to_owned()));
1779        assert_eq!(format_bool("f", false), Ok("0.000000".to_owned()));
1780        assert_eq!(format_bool("F", true), Ok("1.000000".to_owned()));
1781        assert_eq!(format_bool("F", false), Ok("0.000000".to_owned()));
1782        assert_eq!(format_bool("%", true), Ok("100.000000%".to_owned()));
1783        assert_eq!(format_bool("%", false), Ok("0.000000%".to_owned()));
1784    }
1785
1786    #[test]
1787    fn format_bool_without_a_presentation_type() {
1788        // The bare spec is the only one that spells the value out.
1789        assert_eq!(format_bool("", true), Ok("True".to_owned()));
1790        assert_eq!(format_bool("", false), Ok("False".to_owned()));
1791
1792        // Anything else formats the integer, the way `int.__format__` would.
1793        assert_eq!(format_bool("5", true), Ok("    1".to_owned()));
1794        assert_eq!(format_bool("<5", true), Ok("1    ".to_owned()));
1795        assert_eq!(format_bool(">5", false), Ok("    0".to_owned()));
1796        assert_eq!(format_bool("^5", true), Ok("  1  ".to_owned()));
1797        assert_eq!(format_bool("05", true), Ok("00001".to_owned()));
1798        assert_eq!(format_bool("+", true), Ok("+1".to_owned()));
1799        assert_eq!(format_bool(" ", false), Ok(" 0".to_owned()));
1800        assert_eq!(format_bool(",", true), Ok("1".to_owned()));
1801        assert_eq!(format_bool("<", true), Ok("1".to_owned()));
1802
1803        // And it inherits the integer rules, precision included.
1804        assert_eq!(
1805            format_bool(".2", true),
1806            Err(FormatSpecError::PrecisionNotAllowed)
1807        );
1808        assert_eq!(
1809            format_bool("z", true),
1810            Err(FormatSpecError::NegativeZeroCoercionNotAllowed("integer"))
1811        );
1812    }
1813
1814    #[test]
1815    fn format_string_zero_padding_uses_left_alignment() {
1816        let spec = FormatSpec::parse("08s").unwrap();
1817        let value = "result".to_owned();
1818
1819        assert_eq!(spec.format_string(&value), Ok("result00".to_owned()));
1820    }
1821
1822    #[test]
1823    fn format_string_explicit_after_sign_alignment_is_invalid() {
1824        let spec = FormatSpec::parse("=8s").unwrap();
1825        let value = "result".to_owned();
1826
1827        assert_eq!(
1828            spec.format_string(&value),
1829            Err(FormatSpecError::StringAlignmentFlag)
1830        );
1831    }
1832
1833    #[test]
1834    fn format_string_rejects_sign_space_and_alternate_form() {
1835        let value = "result".to_owned();
1836        let cases = [
1837            ("+", "Sign"),
1838            ("-", "Sign"),
1839            ("+8s", "Sign"),
1840            (" ", "Space"),
1841            (" 8s", "Space"),
1842            ("#", "Alternate form (#)"),
1843            ("#8s", "Alternate form (#)"),
1844        ];
1845
1846        for (text, flag) in cases {
1847            let spec = FormatSpec::parse(text).unwrap();
1848            assert_eq!(
1849                spec.format_string(&value),
1850                Err(FormatSpecError::StringSpecNotAllowed(flag)),
1851                "{text}"
1852            );
1853        }
1854    }
1855
1856    #[test]
1857    fn format_string_reports_the_flag_cpython_reports_first() {
1858        let value = "result".to_owned();
1859        // Sign beats z, z beats the alternate form, and the alternate form
1860        // beats an explicit '=' alignment.
1861        let cases = [
1862            ("+z#5", FormatSpecError::StringSpecNotAllowed("Sign")),
1863            (
1864                "x=z#5",
1865                FormatSpecError::NegativeZeroCoercionNotAllowed("string"),
1866            ),
1867            (
1868                "x=#5",
1869                FormatSpecError::StringSpecNotAllowed("Alternate form (#)"),
1870            ),
1871        ];
1872
1873        for (text, expected) in cases {
1874            let spec = FormatSpec::parse(text).unwrap();
1875            assert_eq!(spec.format_string(&value), Err(expected), "{text}");
1876        }
1877    }
1878
1879    #[test]
1880    fn format_complex_rejects_zero_padding_before_after_sign_alignment() {
1881        for text in [
1882            "08.1f", "=08.1f", "0=8.1f", "#08.1f", "0>8.1f", "0<8.1f", "0^8.1f",
1883        ] {
1884            let spec = FormatSpec::parse(text).unwrap();
1885            assert_eq!(
1886                spec.format_complex(&Complex64::new(1.0, 2.0)),
1887                Err(FormatSpecError::ZeroPadding),
1888                "{text}"
1889            );
1890        }
1891
1892        let spec = FormatSpec::parse("=8.1f").unwrap();
1893        assert_eq!(
1894            spec.format_complex(&Complex64::new(1.0, 2.0)),
1895            Err(FormatSpecError::AlignmentFlag)
1896        );
1897    }
1898
1899    #[test]
1900    fn format_int_zero_padding_stays_after_sign() {
1901        let spec = FormatSpec::parse("08").unwrap();
1902
1903        assert_eq!(
1904            spec.format_int(&BigInt::from(-42)),
1905            Ok("-0000042".to_owned())
1906        );
1907    }
1908
1909    #[test]
1910    fn format_complex_locale_rejects_zero_padding_before_after_sign_alignment() {
1911        let locale = LocaleInfo {
1912            thousands_sep: String::new(),
1913            decimal_point: ".".to_owned(),
1914            grouping: vec![],
1915        };
1916        for text in ["08n", "=08n", "0=8n", "#08n", "0>8n", "0<8n", "0^8n"] {
1917            let spec = FormatSpec::parse(text).unwrap();
1918            assert_eq!(
1919                spec.format_complex_locale(&Complex64::new(1.0, 2.0), &locale),
1920                Err(FormatSpecError::ZeroPadding),
1921                "{text}"
1922            );
1923        }
1924
1925        let spec = FormatSpec::parse("=8n").unwrap();
1926        assert_eq!(
1927            spec.format_complex_locale(&Complex64::new(1.0, 2.0), &locale),
1928            Err(FormatSpecError::AlignmentFlag)
1929        );
1930    }
1931
1932    #[test]
1933    fn format_negative_zero_coercion() {
1934        let int_spec = FormatSpec::parse("z8").unwrap();
1935        assert_eq!(
1936            int_spec.format_int(&BigInt::from(-42)),
1937            Err(FormatSpecError::NegativeZeroCoercionNotAllowed("integer"))
1938        );
1939        assert_eq!(
1940            FormatSpec::parse("zs")
1941                .unwrap()
1942                .format_int(&BigInt::from(0)),
1943            Err(FormatSpecError::UnknownFormatCode('s', "int"))
1944        );
1945        assert_eq!(
1946            FormatSpec::parse("z.1d")
1947                .unwrap()
1948                .format_int(&BigInt::from(0)),
1949            Err(FormatSpecError::PrecisionNotAllowed)
1950        );
1951        assert_eq!(
1952            FormatSpec::parse("+zc")
1953                .unwrap()
1954                .format_int(&BigInt::from(0)),
1955            Err(FormatSpecError::NegativeZeroCoercionNotAllowed("integer"))
1956        );
1957
1958        let float_spec = FormatSpec::parse("z.2f").unwrap();
1959        assert_eq!(float_spec.format_float(-0.0001), Ok("0.00".to_owned()));
1960
1961        let complex_spec = FormatSpec::parse("z").unwrap();
1962        assert_eq!(
1963            complex_spec.format_complex(&Complex64::new(-0.0, -0.0)),
1964            Ok("(0+0j)".to_owned())
1965        );
1966        let pure_imaginary = Complex64::new(0.0, -0.0);
1967        assert_eq!(
1968            FormatSpec::parse("+z")
1969                .unwrap()
1970                .format_complex(&pure_imaginary),
1971            Ok("+0j".to_owned())
1972        );
1973        assert_eq!(
1974            FormatSpec::parse(" z")
1975                .unwrap()
1976                .format_complex(&pure_imaginary),
1977            Ok(" 0j".to_owned())
1978        );
1979
1980        let string_value = "value".to_owned();
1981        assert_eq!(
1982            FormatSpec::parse("z").unwrap().format_string(&string_value),
1983            Err(FormatSpecError::NegativeZeroCoercionNotAllowed("string"))
1984        );
1985        assert_eq!(
1986            FormatSpec::parse("zd")
1987                .unwrap()
1988                .format_string(&string_value),
1989            Err(FormatSpecError::UnknownFormatCode('d', "str"))
1990        );
1991        assert_eq!(
1992            FormatSpec::parse("zs").unwrap().format_bool(false),
1993            Err(FormatSpecError::UnknownFormatCode('s', "bool"))
1994        );
1995
1996        let locale = LocaleInfo {
1997            thousands_sep: ",".to_owned(),
1998            decimal_point: ".".to_owned(),
1999            grouping: vec![3, 0],
2000        };
2001        let locale_spec = FormatSpec::parse("zn").unwrap();
2002        assert_eq!(
2003            locale_spec.format_float_locale(-0.0, &locale),
2004            Ok("0".to_owned())
2005        );
2006        assert_eq!(
2007            locale_spec.format_complex_locale(&Complex64::new(-0.0, -0.0), &locale),
2008            Ok("0+0j".to_owned())
2009        );
2010        assert_eq!(
2011            FormatSpec::parse("z.1n")
2012                .unwrap()
2013                .format_int_locale(&BigInt::from(0), &locale),
2014            Err(FormatSpecError::PrecisionNotAllowed)
2015        );
2016    }
2017
2018    #[test]
2019    fn format_int() {
2020        assert_eq!(
2021            FormatSpec::parse("d")
2022                .unwrap()
2023                .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
2024            Ok("16".to_owned())
2025        );
2026        assert_eq!(
2027            FormatSpec::parse("x")
2028                .unwrap()
2029                .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
2030            Ok("10".to_owned())
2031        );
2032        assert_eq!(
2033            FormatSpec::parse("b")
2034                .unwrap()
2035                .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
2036            Ok("10000".to_owned())
2037        );
2038        assert_eq!(
2039            FormatSpec::parse("o")
2040                .unwrap()
2041                .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
2042            Ok("20".to_owned())
2043        );
2044        assert_eq!(
2045            FormatSpec::parse("+d")
2046                .unwrap()
2047                .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
2048            Ok("+16".to_owned())
2049        );
2050        assert_eq!(
2051            FormatSpec::parse("^ 5d")
2052                .unwrap()
2053                .format_int(&BigInt::from_bytes_be(Sign::Minus, b"\x10")),
2054            Ok(" -16 ".to_owned())
2055        );
2056        assert_eq!(
2057            FormatSpec::parse("0>+#10x")
2058                .unwrap()
2059                .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
2060            Ok("00000+0x10".to_owned())
2061        );
2062    }
2063
2064    #[test]
2065    fn format_int_sep() {
2066        let spec = FormatSpec::parse(",").expect("");
2067        assert_eq!(spec.grouping_option, Some(FormatGrouping::Comma));
2068        assert_eq!(
2069            spec.format_int(&BigInt::from_str("1234567890123456789012345678").unwrap()),
2070            Ok("1,234,567,890,123,456,789,012,345,678".to_owned())
2071        );
2072    }
2073
2074    #[test]
2075    fn format_int_width_and_grouping() {
2076        // issue #5922: width + comma grouping should pad left, not inside the number
2077        let spec = FormatSpec::parse("10,").unwrap();
2078        let result = spec.format_int(&BigInt::from(1234)).unwrap();
2079        assert_eq!(result, "     1,234"); // CPython 3.13.5
2080    }
2081
2082    #[test]
2083    fn format_int_padding_with_grouping() {
2084        // CPython behavior: f'{1234:010,}' results in "00,001,234"
2085        let spec1 = FormatSpec::parse("010,").unwrap();
2086        let result1 = spec1.format_int(&BigInt::from(1234)).unwrap();
2087        assert_eq!(result1, "00,001,234");
2088
2089        // CPython behavior: f'{-1234:010,}' results in "-0,001,234"
2090        let spec2 = FormatSpec::parse("010,").unwrap();
2091        let result2 = spec2.format_int(&BigInt::from(-1234)).unwrap();
2092        assert_eq!(result2, "-0,001,234");
2093
2094        // CPython behavior: f'{-1234:=10,}' results in "-    1,234"
2095        let spec3 = FormatSpec::parse("=10,").unwrap();
2096        let result3 = spec3.format_int(&BigInt::from(-1234)).unwrap();
2097        assert_eq!(result3, "-    1,234");
2098
2099        // CPython behavior: f'{1234:=10,}' results in "     1,234" (same as right-align for positive numbers)
2100        let spec4 = FormatSpec::parse("=10,").unwrap();
2101        let result4 = spec4.format_int(&BigInt::from(1234)).unwrap();
2102        assert_eq!(result4, "     1,234");
2103    }
2104
2105    #[test]
2106    fn format_int_non_aftersign_zero_padding() {
2107        // CPython behavior: f'{1234:0>10,}' results in "000001,234"
2108        let spec = FormatSpec::parse("0>10,").unwrap();
2109        let result = spec.format_int(&BigInt::from(1234)).unwrap();
2110        assert_eq!(result, "000001,234");
2111    }
2112
2113    fn fmt_float(spec: &str, value: f64) -> String {
2114        FormatSpec::parse(spec)
2115            .unwrap()
2116            .format_float(value)
2117            .unwrap()
2118    }
2119
2120    #[test]
2121    fn format_float_grouping_never_touches_exponent() {
2122        // Grouping must group only the mantissa's integer digits, never the
2123        // exponent digits (was "1e,+20") or a trailing percent sign.
2124        assert_eq!(fmt_float(",g", 1e20), "1e+20");
2125        assert_eq!(fmt_float("_g", 1e-10), "1e-10");
2126        assert_eq!(fmt_float(",e", 1e20), "1.000000e+20");
2127        assert_eq!(fmt_float(",", 1e16), "1e+16");
2128        assert_eq!(fmt_float(",.0%", 1.0), "100%");
2129        assert_eq!(fmt_float(",.2%", 12345.0), "1,234,500.00%");
2130        // Fixed-form grouping still groups the integer part.
2131        assert_eq!(fmt_float(",", 1234567.0), "1,234,567.0");
2132    }
2133
2134    #[test]
2135    fn format_float_grouping_inf_nan() {
2136        // No integer digits to group; width padding is left to fill/align, so
2137        // separators never land inside "inf"/"nan".
2138        assert_eq!(fmt_float(",", f64::INFINITY), "inf");
2139        assert_eq!(fmt_float("06,", f64::INFINITY), "000inf");
2140        assert_eq!(fmt_float("06,", f64::NAN), "000nan");
2141        assert_eq!(fmt_float("06,%", f64::INFINITY), "00inf%");
2142    }
2143
2144    #[test]
2145    fn format_float_fractional_grouping() {
2146        // Fraction digits group away from the decimal point, so the last group
2147        // may be shorter than the interval.
2148        assert_eq!(fmt_float(".6,f", 1234.56789), "1234.567,890");
2149        assert_eq!(fmt_float(".7,f", 1234.56789), "1234.567,890,0");
2150        assert_eq!(fmt_float(".4,f", 1.1), "1.100,0");
2151        assert_eq!(fmt_float(".3,f", 1.1), "1.100");
2152        assert_eq!(fmt_float(".6_f", 1234.56789), "1234.567_890");
2153        // Omitting the precision keeps the type's default.
2154        assert_eq!(fmt_float(".,f", 1.1), "1.100,000");
2155        // The two parts are independent and may use different separators.
2156        assert_eq!(fmt_float(",.6,f", 1234.56789), "1,234.567,890");
2157        assert_eq!(fmt_float(",.6_f", 1234.56789), "1,234.567_890");
2158        assert_eq!(fmt_float("_.6,f", 1234.56789), "1_234.567,890");
2159    }
2160
2161    #[test]
2162    fn format_float_fractional_grouping_never_touches_tail() {
2163        // Only the digits between the point and any tail are groupable: the
2164        // exponent and a trailing percent sign must stay intact.
2165        assert_eq!(fmt_float(".6,e", 12345678900.0), "1.234,568e+10");
2166        assert_eq!(fmt_float(".6,E", 1234.5678), "1.234,568E+03");
2167        assert_eq!(fmt_float(".8,%", 1.2345e-05), "0.001,234,50%");
2168        // Values with no point have nothing to group.
2169        assert_eq!(fmt_float(".6,f", f64::INFINITY), "inf");
2170        assert_eq!(fmt_float(".6,f", f64::NAN), "nan");
2171        assert_eq!(fmt_float(".0,f", 1234.56789), "1235");
2172    }
2173
2174    #[test]
2175    fn format_float_fractional_grouping_counts_toward_width() {
2176        // Separators are inserted before padding, so they consume width.
2177        assert_eq!(fmt_float("020.6,f", 1234.56789), "000000001234.567,890");
2178        assert_eq!(fmt_float("015.6,f", 1.5), "0000001.500,000");
2179        assert_eq!(fmt_float("<20.6,f", 1234.56789), "1234.567,890        ");
2180        // Zero padding of the integer part must reserve room for them too.
2181        assert_eq!(fmt_float("020,.6,f", 1234.56789), "0,000,001,234.567,890");
2182        assert_eq!(fmt_float("+020,.6_f", 1e-10), "+000,000,000.000_000");
2183        assert_eq!(fmt_float("= 015,.6,E", 1234.0), " 01.234,000E+03");
2184        assert_eq!(fmt_float("-015_._e", 1.1), "001.100_000e+00");
2185    }
2186
2187    #[test]
2188    fn format_parse_fractional_grouping_errors() {
2189        // Mixing the two separators is rejected wherever it appears.
2190        assert_eq!(
2191            FormatSpec::parse(".,_f"),
2192            Err(FormatSpecError::ExclusiveFormat(',', '_'))
2193        );
2194        assert_eq!(
2195            FormatSpec::parse("._,f"),
2196            Err(FormatSpecError::ExclusiveFormat(',', '_'))
2197        );
2198        // A repeated separator is left in the spec and rejected as a whole.
2199        assert_eq!(
2200            FormatSpec::parse(".,,f"),
2201            Err(FormatSpecError::InvalidFormatSpecifier)
2202        );
2203        assert_eq!(
2204            FormatSpec::parse(".__f"),
2205            Err(FormatSpecError::InvalidFormatSpecifier)
2206        );
2207        // A dot needs either digits or a separator after it.
2208        assert_eq!(
2209            FormatSpec::parse("."),
2210            Err(FormatSpecError::PrecisionMissing)
2211        );
2212        assert_eq!(
2213            FormatSpec::parse(".f"),
2214            Err(FormatSpecError::PrecisionMissing)
2215        );
2216        // 'n' draws its separators from the locale.
2217        assert_eq!(
2218            FormatSpec::parse(".6,n").unwrap().format_float(1234.5678),
2219            Err(FormatSpecError::UnspecifiedFormat(',', 'n'))
2220        );
2221        assert_eq!(
2222            FormatSpec::parse("._n").unwrap().format_float(1234.5678),
2223            Err(FormatSpecError::UnspecifiedFormat('_', 'n'))
2224        );
2225        // The integer separator is reported first when both are present.
2226        assert_eq!(
2227            FormatSpec::parse("_.6,n").unwrap().format_float(1234.5678),
2228            Err(FormatSpecError::UnspecifiedFormat('_', 'n'))
2229        );
2230        // The complex locale path rewrites 'n' to 'g', so it must validate first.
2231        let locale = LocaleInfo {
2232            thousands_sep: ",".to_owned(),
2233            decimal_point: ".".to_owned(),
2234            grouping: vec![3, 0],
2235        };
2236        assert_eq!(
2237            FormatSpec::parse(".6,n")
2238                .unwrap()
2239                .format_complex_locale(&Complex64::new(1.0, 2.345678), &locale),
2240            Err(FormatSpecError::UnspecifiedFormat(',', 'n'))
2241        );
2242    }
2243
2244    #[test]
2245    fn format_float_empty_type_with_precision() {
2246        // Empty presentation type with a precision is repr-like: precision is
2247        // clamped to at least 1 and integer-looking output keeps a `.0`.
2248        assert_eq!(fmt_float(".2", 1.0), "1.0");
2249        assert_eq!(fmt_float(".6", 100.0), "100.0");
2250        assert_eq!(fmt_float(".17", 1234567.0), "1234567.0");
2251        assert_eq!(fmt_float(".0", 0.5), "0.5");
2252        assert_eq!(fmt_float(".0", 0.0001), "0.0001");
2253        assert_eq!(fmt_float(".2", 0.0), "0.0");
2254        assert_eq!(fmt_float(".0", 0.0), "0e+00");
2255        assert_eq!(fmt_float(".2", 100.0), "1e+02");
2256    }
2257
2258    #[test]
2259    fn format_float_alternate_form_forces_point() {
2260        // Alternate form injects a decimal point into exponent-form repr.
2261        assert_eq!(fmt_float("#", 1e16), "1.e+16");
2262        assert_eq!(fmt_float("#", 1e-5), "1.e-05");
2263        // Fixed-form repr already has a point, so it is unchanged.
2264        assert_eq!(fmt_float("#", 100.0), "100.0");
2265        assert_eq!(fmt_float("#", 1.5), "1.5");
2266    }
2267
2268    #[test]
2269    fn format_int_hex_grouping_preserved() {
2270        // Underscore grouping of hex/octal groups every 4 digits, including the
2271        // `a`-`f` letters.
2272        assert_eq!(
2273            FormatSpec::parse("_x")
2274                .unwrap()
2275                .format_int(&BigInt::from(1000000))
2276                .unwrap(),
2277            "f_4240"
2278        );
2279        assert_eq!(
2280            FormatSpec::parse("_X")
2281                .unwrap()
2282                .format_int(&BigInt::from(0xABCDEFu32))
2283                .unwrap(),
2284            "AB_CDEF"
2285        );
2286    }
2287
2288    #[test]
2289    fn format_int_character_rejects_precision() {
2290        // 'c' rejects precision, and precision is checked before sign/alt form.
2291        assert_eq!(
2292            FormatSpec::parse(".2c")
2293                .unwrap()
2294                .format_int(&BigInt::from(65)),
2295            Err(FormatSpecError::PrecisionNotAllowed)
2296        );
2297        assert_eq!(
2298            FormatSpec::parse("+.2c")
2299                .unwrap()
2300                .format_int(&BigInt::from(65)),
2301            Err(FormatSpecError::PrecisionNotAllowed)
2302        );
2303        // Without precision, 'c' still renders the code point.
2304        assert_eq!(
2305            FormatSpec::parse("c")
2306                .unwrap()
2307                .format_int(&BigInt::from(65)),
2308            Ok("A".to_owned())
2309        );
2310    }
2311
2312    #[test]
2313    fn format_parse() {
2314        let expected = Ok(FormatString {
2315            format_parts: vec![
2316                FormatPart::Literal("abcd".into()),
2317                FormatPart::Field {
2318                    field_name: "1".into(),
2319                    conversion_spec: None,
2320                    format_spec: "".into(),
2321                },
2322                FormatPart::Literal(":".into()),
2323                FormatPart::Field {
2324                    field_name: "key".into(),
2325                    conversion_spec: None,
2326                    format_spec: "".into(),
2327                },
2328            ],
2329        });
2330
2331        assert_eq!(FormatString::from_str("abcd{1}:{key}".as_ref()), expected);
2332    }
2333
2334    #[test]
2335    fn format_parse_multi_byte_char() {
2336        assert!(FormatString::from_str("{a:%ЫйЯЧ}".as_ref()).is_ok());
2337    }
2338
2339    #[test]
2340    fn format_parse_fail() {
2341        assert_eq!(
2342            FormatString::from_str("{s".as_ref()),
2343            Err(FormatParseError::UnmatchedBracket)
2344        );
2345    }
2346
2347    #[test]
2348    fn format_parse_lone_start_bracket() {
2349        // A brace with nothing after it is a stray brace; one holding a field
2350        // that was never closed is not. CPython words the two differently.
2351        for lone in ["{", "a{"] {
2352            assert_eq!(
2353                FormatString::from_str(lone.as_ref()),
2354                Err(FormatParseError::UnescapedStartBracketInLiteral),
2355                "{lone:?}"
2356            );
2357        }
2358        for unclosed in ["{s", "a{b", "{0"] {
2359            assert_eq!(
2360                FormatString::from_str(unclosed.as_ref()),
2361                Err(FormatParseError::UnmatchedBracket),
2362                "{unclosed:?}"
2363            );
2364        }
2365    }
2366
2367    #[test]
2368    fn square_brackets_inside_format() {
2369        assert_eq!(
2370            FormatString::from_str("{[:123]}".as_ref()),
2371            Ok(FormatString {
2372                format_parts: vec![FormatPart::Field {
2373                    field_name: "[:123]".into(),
2374                    conversion_spec: None,
2375                    format_spec: "".into(),
2376                }],
2377            }),
2378        );
2379
2380        assert_eq!(FormatString::from_str("{asdf[:123]asdf}".as_ref()), {
2381            Ok(FormatString {
2382                format_parts: vec![FormatPart::Field {
2383                    field_name: "asdf[:123]asdf".into(),
2384                    conversion_spec: None,
2385                    format_spec: "".into(),
2386                }],
2387            })
2388        });
2389
2390        assert_eq!(FormatString::from_str("{[1234}".as_ref()), {
2391            Err(FormatParseError::MissingRightBracket)
2392        });
2393    }
2394
2395    #[test]
2396    fn format_parse_escape() {
2397        let expected = Ok(FormatString {
2398            format_parts: vec![
2399                FormatPart::Literal("{".into()),
2400                FormatPart::Field {
2401                    field_name: "key".into(),
2402                    conversion_spec: None,
2403                    format_spec: "".into(),
2404                },
2405                FormatPart::Literal("}ddfe".into()),
2406            ],
2407        });
2408
2409        assert_eq!(FormatString::from_str("{{{key}}}ddfe".as_ref()), expected);
2410    }
2411
2412    #[test]
2413    fn format_invalid_specification() {
2414        assert_eq!(
2415            FormatSpec::parse("%3"),
2416            Err(FormatSpecError::InvalidFormatSpecifier)
2417        );
2418        assert_eq!(
2419            FormatSpec::parse(".2fa"),
2420            Err(FormatSpecError::InvalidFormatSpecifier)
2421        );
2422        assert_eq!(
2423            FormatSpec::parse("ds"),
2424            Err(FormatSpecError::InvalidFormatSpecifier)
2425        );
2426        assert_eq!(
2427            FormatSpec::parse("x+"),
2428            Err(FormatSpecError::InvalidFormatSpecifier)
2429        );
2430        assert_eq!(
2431            FormatSpec::parse("b4"),
2432            Err(FormatSpecError::InvalidFormatSpecifier)
2433        );
2434        assert_eq!(
2435            FormatSpec::parse("o!"),
2436            Err(FormatSpecError::InvalidFormatSpecifier)
2437        );
2438        assert_eq!(
2439            FormatSpec::parse("d "),
2440            Err(FormatSpecError::InvalidFormatSpecifier)
2441        );
2442    }
2443
2444    #[test]
2445    fn parse_field_name() {
2446        let parse = |s: &str| FieldName::parse(s.as_ref());
2447        assert_eq!(
2448            parse(""),
2449            Ok(FieldName {
2450                field_type: FieldType::Auto,
2451                parts: Vec::new(),
2452            })
2453        );
2454        assert_eq!(
2455            parse("0"),
2456            Ok(FieldName {
2457                field_type: FieldType::Index(0),
2458                parts: Vec::new(),
2459            })
2460        );
2461        assert_eq!(
2462            parse("key"),
2463            Ok(FieldName {
2464                field_type: FieldType::Keyword("key".into()),
2465                parts: Vec::new(),
2466            })
2467        );
2468        assert_eq!(
2469            parse("key.attr[0][string]"),
2470            Ok(FieldName {
2471                field_type: FieldType::Keyword("key".into()),
2472                parts: vec![
2473                    FieldNamePart::Attribute("attr".into()),
2474                    FieldNamePart::Index(0),
2475                    FieldNamePart::StringIndex("string".into())
2476                ],
2477            })
2478        );
2479        assert_eq!(parse("key.."), Err(FormatParseError::EmptyAttribute));
2480        assert_eq!(parse("key[]"), Err(FormatParseError::EmptyAttribute));
2481        assert_eq!(parse("key["), Err(FormatParseError::MissingRightBracket));
2482        assert_eq!(
2483            parse("key[0]after"),
2484            Err(FormatParseError::InvalidCharacterAfterRightBracket)
2485        );
2486    }
2487}