Skip to main content

rustpython_common/
cformat.rs

1//! Implementation of Printf-Style string formatting
2//! as per the [Python Docs](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting).
3use alloc::fmt;
4use bitflags::bitflags;
5use core::{
6    cmp,
7    iter::{Enumerate, Peekable},
8    str::FromStr,
9};
10use itertools::Itertools;
11use malachite_bigint::{BigInt, Sign};
12use num_traits::Signed;
13use rustpython_literal::{float, format::Case};
14
15use crate::wtf8::{CodePoint, Wtf8, Wtf8Buf};
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum CFormatErrorType {
19    UnmatchedKeyParentheses,
20    MissingModuloSign,
21    UnsupportedFormatChar(CodePoint),
22    IncompleteFormat,
23    WidthTooBig,
24    PrecisionTooBig,
25    // Unimplemented,
26}
27
28// also contains how many chars the parsing function consumed
29pub type ParsingError = (CFormatErrorType, usize);
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub struct CFormatError {
33    pub typ: CFormatErrorType, // FIXME
34    pub index: usize,
35}
36
37impl fmt::Display for CFormatError {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        match self.typ {
40            CFormatErrorType::UnmatchedKeyParentheses => write!(f, "incomplete format key"),
41            CFormatErrorType::IncompleteFormat => write!(f, "incomplete format"),
42            CFormatErrorType::UnsupportedFormatChar(c) => write!(
43                f,
44                "unsupported format character '{}' ({:#x}) at index {}",
45                c,
46                c.to_u32(),
47                self.index
48            ),
49            CFormatErrorType::WidthTooBig => write!(f, "width too big"),
50            CFormatErrorType::PrecisionTooBig => write!(f, "precision too big"),
51            _ => write!(f, "unexpected error parsing format string"),
52        }
53    }
54}
55
56pub type CFormatConversion = super::format::FormatConversion;
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum CFormatContext {
60    Str,
61    Bytes,
62}
63
64#[derive(Debug, PartialEq, Eq, Clone, Copy)]
65#[repr(u8)]
66pub enum CNumberType {
67    DecimalD = b'd',
68    DecimalI = b'i',
69    DecimalU = b'u',
70    Octal = b'o',
71    HexLower = b'x',
72    HexUpper = b'X',
73}
74
75#[derive(Debug, PartialEq, Eq, Clone, Copy)]
76#[repr(u8)]
77pub enum CFloatType {
78    ExponentLower = b'e',
79    ExponentUpper = b'E',
80    PointDecimalLower = b'f',
81    PointDecimalUpper = b'F',
82    GeneralLower = b'g',
83    GeneralUpper = b'G',
84}
85
86impl CFloatType {
87    const fn case(self) -> Case {
88        match self {
89            Self::ExponentLower | Self::PointDecimalLower | Self::GeneralLower => Case::Lower,
90            Self::ExponentUpper | Self::PointDecimalUpper | Self::GeneralUpper => Case::Upper,
91        }
92    }
93}
94
95#[derive(Debug, PartialEq, Eq, Clone, Copy)]
96#[repr(u8)]
97pub enum CCharacterType {
98    Character = b'c',
99}
100
101#[derive(Debug, PartialEq, Eq, Clone, Copy)]
102pub enum CFormatType {
103    Number(CNumberType),
104    Float(CFloatType),
105    Character(CCharacterType),
106    String(CFormatConversion),
107    Bytes,
108    /// Parsed conversion that is not one of the supported specifiers.
109    /// The argument is still consumed, then formatting raises ValueError.
110    Unsupported {
111        ch: CodePoint,
112        index: usize,
113    },
114}
115
116impl CFormatType {
117    #[must_use]
118    pub fn to_char(self) -> char {
119        match self {
120            Self::Number(x) => x as u8 as char,
121            Self::Float(x) => x as u8 as char,
122            Self::Character(x) => x as u8 as char,
123            Self::String(x) => x as u8 as char,
124            Self::Bytes => 'b',
125            Self::Unsupported { ch, .. } => ch.to_char_lossy(),
126        }
127    }
128}
129
130#[derive(Debug, PartialEq, Eq, Clone, Copy)]
131pub enum CFormatPrecision {
132    Quantity(CFormatQuantity),
133    Dot,
134}
135
136impl From<CFormatQuantity> for CFormatPrecision {
137    fn from(quantity: CFormatQuantity) -> Self {
138        Self::Quantity(quantity)
139    }
140}
141
142bitflags! {
143    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
144    pub struct CConversionFlags: u32 {
145        const ALTERNATE_FORM = 0b0000_0001;
146        const ZERO_PAD = 0b0000_0010;
147        const LEFT_ADJUST = 0b0000_0100;
148        const BLANK_SIGN = 0b0000_1000;
149        const SIGN_CHAR = 0b0001_0000;
150    }
151}
152
153impl CConversionFlags {
154    #[inline]
155    #[must_use]
156    pub const fn sign_string(&self) -> &'static str {
157        if self.contains(Self::SIGN_CHAR) {
158            "+"
159        } else if self.contains(Self::BLANK_SIGN) {
160            " "
161        } else {
162            ""
163        }
164    }
165}
166
167#[derive(Debug, PartialEq, Eq, Clone, Copy)]
168pub enum CFormatQuantity {
169    Amount(usize),
170    FromValuesTuple,
171}
172
173pub trait FormatBuf:
174    Extend<Self::Char> + Default + FromIterator<Self::Char> + From<String>
175{
176    type Char: FormatChar;
177
178    fn chars(&self) -> impl Iterator<Item = Self::Char>;
179
180    fn len(&self) -> usize;
181
182    fn is_empty(&self) -> bool {
183        self.len() == 0
184    }
185
186    #[must_use]
187    fn concat(self, other: Self) -> Self;
188}
189
190pub trait FormatChar: Copy + Into<CodePoint> + From<u8> {
191    fn to_char_lossy(self) -> char;
192
193    fn eq_char(self, c: char) -> bool;
194}
195
196impl FormatBuf for String {
197    type Char = char;
198
199    fn chars(&self) -> impl Iterator<Item = Self::Char> {
200        (**self).chars()
201    }
202
203    fn len(&self) -> usize {
204        self.len()
205    }
206
207    fn concat(mut self, other: Self) -> Self {
208        self.extend([other]);
209        self
210    }
211}
212
213impl FormatChar for char {
214    fn to_char_lossy(self) -> char {
215        self
216    }
217
218    fn eq_char(self, c: char) -> bool {
219        self == c
220    }
221}
222
223impl FormatBuf for Wtf8Buf {
224    type Char = CodePoint;
225
226    fn chars(&self) -> impl Iterator<Item = Self::Char> {
227        self.code_points()
228    }
229
230    fn len(&self) -> usize {
231        (**self).len()
232    }
233
234    fn concat(mut self, other: Self) -> Self {
235        self.extend([other]);
236        self
237    }
238}
239
240impl FormatChar for CodePoint {
241    fn to_char_lossy(self) -> char {
242        self.to_char_lossy()
243    }
244
245    fn eq_char(self, c: char) -> bool {
246        self == c
247    }
248}
249
250impl FormatBuf for Vec<u8> {
251    type Char = u8;
252
253    fn chars(&self) -> impl Iterator<Item = Self::Char> {
254        self.iter().copied()
255    }
256
257    fn len(&self) -> usize {
258        self.len()
259    }
260
261    fn concat(mut self, other: Self) -> Self {
262        self.extend(other);
263        self
264    }
265}
266
267impl FormatChar for u8 {
268    fn to_char_lossy(self) -> char {
269        self.into()
270    }
271
272    fn eq_char(self, c: char) -> bool {
273        char::from(self) == c
274    }
275}
276
277#[derive(Clone, Copy, Debug, Eq, PartialEq)]
278pub struct CFormatSpec {
279    pub flags: CConversionFlags,
280    pub min_field_width: Option<CFormatQuantity>,
281    pub precision: Option<CFormatPrecision>,
282    pub format_type: CFormatType,
283    // chars_consumed: usize,
284}
285
286#[derive(Debug, Eq, PartialEq)]
287pub struct CFormatSpecKeyed<T> {
288    pub mapping_key: Option<T>,
289    pub spec: CFormatSpec,
290}
291
292#[cfg(test)]
293impl FromStr for CFormatSpec {
294    type Err = ParsingError;
295
296    fn from_str(text: &str) -> Result<Self, Self::Err> {
297        text.parse::<CFormatSpecKeyed<String>>()
298            .map(|CFormatSpecKeyed { mapping_key, spec }| {
299                assert!(mapping_key.is_none());
300                spec
301            })
302    }
303}
304
305impl FromStr for CFormatSpecKeyed<String> {
306    type Err = ParsingError;
307
308    fn from_str(text: &str) -> Result<Self, Self::Err> {
309        let mut chars = text.chars().enumerate().peekable();
310        if chars.next().map(|x| x.1) != Some('%') {
311            return Err((CFormatErrorType::MissingModuloSign, 1));
312        }
313
314        Self::parse(&mut chars, CFormatContext::Str)
315    }
316}
317
318pub type ParseIter<I> = Peekable<Enumerate<I>>;
319
320impl<T: FormatBuf> CFormatSpecKeyed<T> {
321    pub fn parse<I>(iter: &mut ParseIter<I>, context: CFormatContext) -> Result<Self, ParsingError>
322    where
323        I: Iterator<Item = T::Char>,
324    {
325        let mapping_key = parse_spec_mapping_key(iter)?;
326        let flags = parse_flags(iter);
327        let min_field_width =
328            parse_quantity(iter, isize::MAX as usize, CFormatErrorType::WidthTooBig)?;
329        let precision = parse_precision(iter)?;
330        consume_length(iter);
331        let format_type = parse_format_type(iter, context)?;
332
333        let spec = CFormatSpec {
334            flags,
335            min_field_width,
336            precision,
337            format_type,
338        };
339        Ok(Self { mapping_key, spec })
340    }
341}
342
343impl CFormatSpec {
344    fn compute_fill_string<T: FormatBuf>(fill_char: T::Char, fill_chars_needed: usize) -> T {
345        (0..fill_chars_needed).map(|_| fill_char).collect()
346    }
347
348    fn fill_string<T: FormatBuf>(
349        &self,
350        string: T,
351        fill_char: T::Char,
352        num_prefix_chars: Option<usize>,
353    ) -> T {
354        let mut num_chars = string.chars().count();
355        if let Some(num_prefix_chars) = num_prefix_chars {
356            num_chars += num_prefix_chars;
357        }
358        let num_chars = num_chars;
359
360        let width = match &self.min_field_width {
361            Some(CFormatQuantity::Amount(width)) => cmp::max(width, &num_chars),
362            _ => &num_chars,
363        };
364        let fill_chars_needed = width.saturating_sub(num_chars);
365        let fill_string: T = Self::compute_fill_string(fill_char, fill_chars_needed);
366
367        if !fill_string.is_empty() {
368            if self.flags.contains(CConversionFlags::LEFT_ADJUST) {
369                string.concat(fill_string)
370            } else {
371                fill_string.concat(string)
372            }
373        } else {
374            string
375        }
376    }
377
378    fn fill_string_with_precision<T: FormatBuf>(&self, string: T, fill_char: T::Char) -> T {
379        let num_chars = string.chars().count();
380
381        let width = match &self.precision {
382            Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(width))) => {
383                cmp::max(width, &num_chars)
384            }
385            _ => &num_chars,
386        };
387        let fill_chars_needed = width.saturating_sub(num_chars);
388        let fill_string: T = Self::compute_fill_string(fill_char, fill_chars_needed);
389
390        if !fill_string.is_empty() {
391            // Don't left-adjust if precision-filling: that will always be prepending 0s to %d
392            // arguments, the LEFT_ADJUST flag will be used by a later call to fill_string with
393            // the 0-filled string as the string param.
394            fill_string.concat(string)
395        } else {
396            string
397        }
398    }
399
400    fn format_string_with_precision<T: FormatBuf>(
401        &self,
402        string: T,
403        precision: Option<&CFormatPrecision>,
404    ) -> T {
405        // truncate if needed
406        let string = match precision {
407            Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(precision)))
408                if string.chars().count() > *precision =>
409            {
410                string.chars().take(*precision).collect::<T>()
411            }
412            Some(CFormatPrecision::Dot) => {
413                // truncate to 0
414                T::default()
415            }
416            _ => string,
417        };
418        self.fill_string(string, b' '.into(), None)
419    }
420
421    #[inline]
422    pub fn format_string<T: FormatBuf>(&self, string: T) -> T {
423        self.format_string_with_precision(string, self.precision.as_ref())
424    }
425
426    #[inline]
427    pub fn format_char<T: FormatBuf>(&self, ch: T::Char) -> T {
428        self.format_string_with_precision(
429            T::from_iter([ch]),
430            Some(&(CFormatQuantity::Amount(1).into())),
431        )
432    }
433
434    #[must_use]
435    pub fn format_bytes(&self, bytes: &[u8]) -> Vec<u8> {
436        let bytes = match self.precision {
437            Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(precision))) => {
438                &bytes[..cmp::min(bytes.len(), precision)]
439            }
440            // A precision written as a bare dot truncates to nothing.
441            Some(CFormatPrecision::Dot) => &bytes[..0],
442            _ => bytes,
443        };
444        if let Some(CFormatQuantity::Amount(width)) = self.min_field_width {
445            let fill = width.saturating_sub(bytes.len());
446            let mut v = Vec::with_capacity(bytes.len() + fill);
447            if self.flags.contains(CConversionFlags::LEFT_ADJUST) {
448                v.extend_from_slice(bytes);
449                v.append(&mut vec![b' '; fill]);
450            } else {
451                v.append(&mut vec![b' '; fill]);
452                v.extend_from_slice(bytes);
453            }
454            v
455        } else {
456            bytes.to_vec()
457        }
458    }
459
460    #[must_use]
461    pub fn format_number(&self, num: &BigInt) -> String {
462        let CFormatType::Number(format_type) = self.format_type else {
463            unreachable!()
464        };
465
466        let magnitude = num.abs();
467        let prefix = if self.flags.contains(CConversionFlags::ALTERNATE_FORM) {
468            match format_type {
469                CNumberType::Octal => "0o",
470                CNumberType::HexLower => "0x",
471                CNumberType::HexUpper => "0X",
472                _ => "",
473            }
474        } else {
475            ""
476        };
477
478        let magnitude_string: String = match format_type {
479            CNumberType::DecimalD | CNumberType::DecimalI | CNumberType::DecimalU => {
480                magnitude.to_str_radix(10)
481            }
482            CNumberType::Octal => magnitude.to_str_radix(8),
483            CNumberType::HexLower => magnitude.to_str_radix(16),
484            CNumberType::HexUpper => {
485                let mut result = magnitude.to_str_radix(16);
486                result.make_ascii_uppercase();
487                result
488            }
489        };
490
491        let sign_string = match num.sign() {
492            Sign::Minus => "-",
493            _ => self.flags.sign_string(),
494        };
495
496        let padded_magnitude_string = self.fill_string_with_precision(magnitude_string, '0');
497
498        if self.flags.contains(CConversionFlags::ZERO_PAD) {
499            let fill_char = if !self.flags.contains(CConversionFlags::LEFT_ADJUST) {
500                '0'
501            } else {
502                ' ' // '-' overrides the '0' conversion if both are given
503            };
504            let signed_prefix = format!("{sign_string}{prefix}");
505            format!(
506                "{}{}",
507                signed_prefix,
508                self.fill_string(
509                    padded_magnitude_string,
510                    fill_char,
511                    Some(signed_prefix.chars().count()),
512                ),
513            )
514        } else {
515            self.fill_string(
516                format!("{sign_string}{prefix}{padded_magnitude_string}"),
517                ' ',
518                None,
519            )
520        }
521    }
522
523    #[must_use]
524    pub fn format_float(&self, num: f64) -> String {
525        let sign_string = if num.is_sign_negative() && !num.is_nan() {
526            "-"
527        } else {
528            self.flags.sign_string()
529        };
530
531        let precision = match &self.precision {
532            Some(CFormatPrecision::Quantity(quantity)) => match quantity {
533                CFormatQuantity::Amount(amount) => *amount,
534                CFormatQuantity::FromValuesTuple => 6,
535            },
536            Some(CFormatPrecision::Dot) => 0,
537            None => 6,
538        };
539
540        let CFormatType::Float(format_type) = self.format_type else {
541            unreachable!()
542        };
543
544        let magnitude = num.abs();
545        let case = format_type.case();
546
547        let magnitude_string = match format_type {
548            CFloatType::PointDecimalLower | CFloatType::PointDecimalUpper => float::format_fixed(
549                precision,
550                magnitude,
551                case,
552                self.flags.contains(CConversionFlags::ALTERNATE_FORM),
553            ),
554            CFloatType::ExponentLower | CFloatType::ExponentUpper => float::format_exponent(
555                precision,
556                magnitude,
557                case,
558                self.flags.contains(CConversionFlags::ALTERNATE_FORM),
559            ),
560            CFloatType::GeneralLower | CFloatType::GeneralUpper => {
561                let precision = if precision == 0 { 1 } else { precision };
562                float::format_general(
563                    precision,
564                    magnitude,
565                    case,
566                    self.flags.contains(CConversionFlags::ALTERNATE_FORM),
567                    false,
568                )
569            }
570        };
571
572        if self.flags.contains(CConversionFlags::ZERO_PAD) {
573            let fill_char = if !self.flags.contains(CConversionFlags::LEFT_ADJUST) {
574                '0'
575            } else {
576                ' '
577            };
578            format!(
579                "{}{}",
580                sign_string,
581                self.fill_string(
582                    magnitude_string,
583                    fill_char,
584                    Some(sign_string.chars().count()),
585                )
586            )
587        } else {
588            self.fill_string(format!("{sign_string}{magnitude_string}"), ' ', None)
589        }
590    }
591}
592
593fn parse_spec_mapping_key<T, I>(iter: &mut ParseIter<I>) -> Result<Option<T>, ParsingError>
594where
595    T: FormatBuf,
596    I: Iterator<Item = T::Char>,
597{
598    if let Some((index, _)) = iter.next_if(|(_, c)| c.eq_char('(')) {
599        return match parse_text_inside_parentheses(iter) {
600            Some(key) => Ok(Some(key)),
601            None => Err((CFormatErrorType::UnmatchedKeyParentheses, index)),
602        };
603    }
604    Ok(None)
605}
606
607fn parse_flags<C, I>(iter: &mut ParseIter<I>) -> CConversionFlags
608where
609    C: FormatChar,
610    I: Iterator<Item = C>,
611{
612    let mut flags = CConversionFlags::empty();
613    iter.peeking_take_while(|(_, c)| {
614        let flag = match c.to_char_lossy() {
615            '#' => CConversionFlags::ALTERNATE_FORM,
616            '0' => CConversionFlags::ZERO_PAD,
617            '-' => CConversionFlags::LEFT_ADJUST,
618            ' ' => CConversionFlags::BLANK_SIGN,
619            '+' => CConversionFlags::SIGN_CHAR,
620            _ => return false,
621        };
622        flags |= flag;
623        true
624    })
625    .for_each(drop);
626    flags
627}
628
629fn consume_length<C, I>(iter: &mut ParseIter<I>)
630where
631    C: FormatChar,
632    I: Iterator<Item = C>,
633{
634    iter.next_if(|(_, c)| matches!(c.to_char_lossy(), 'h' | 'l' | 'L'));
635}
636
637fn parse_format_type<C, I>(
638    iter: &mut ParseIter<I>,
639    context: CFormatContext,
640) -> Result<CFormatType, ParsingError>
641where
642    C: FormatChar,
643    I: Iterator<Item = C>,
644{
645    let (index, c) = iter.next().ok_or_else(|| {
646        (
647            CFormatErrorType::IncompleteFormat,
648            iter.peek().map_or(0, |x| x.0),
649        )
650    })?;
651
652    Ok(match c.to_char_lossy() {
653        'd' => CFormatType::Number(CNumberType::DecimalD),
654        'i' => CFormatType::Number(CNumberType::DecimalI),
655        'u' => CFormatType::Number(CNumberType::DecimalU),
656        'o' => CFormatType::Number(CNumberType::Octal),
657        'x' => CFormatType::Number(CNumberType::HexLower),
658        'X' => CFormatType::Number(CNumberType::HexUpper),
659        'e' => CFormatType::Float(CFloatType::ExponentLower),
660        'E' => CFormatType::Float(CFloatType::ExponentUpper),
661        'f' => CFormatType::Float(CFloatType::PointDecimalLower),
662        'F' => CFormatType::Float(CFloatType::PointDecimalUpper),
663        'g' => CFormatType::Float(CFloatType::GeneralLower),
664        'G' => CFormatType::Float(CFloatType::GeneralUpper),
665        'c' => CFormatType::Character(CCharacterType::Character),
666        'r' => CFormatType::String(CFormatConversion::Repr),
667        's' => CFormatType::String(CFormatConversion::Str),
668        'a' => CFormatType::String(CFormatConversion::Ascii),
669        'b' if context == CFormatContext::Bytes => CFormatType::Bytes,
670        _ => CFormatType::Unsupported {
671            ch: c.into(),
672            index,
673        },
674    })
675}
676
677fn parse_quantity<C, I>(
678    iter: &mut ParseIter<I>,
679    max_value: usize,
680    too_big: CFormatErrorType,
681) -> Result<Option<CFormatQuantity>, ParsingError>
682where
683    C: FormatChar,
684    I: Iterator<Item = C>,
685{
686    if let Some(&(_, c)) = iter.peek() {
687        if c.eq_char('*') {
688            iter.next().unwrap();
689            return Ok(Some(CFormatQuantity::FromValuesTuple));
690        }
691        if let Some(i) = c.to_char_lossy().to_digit(10) {
692            let mut num = i as usize;
693            iter.next().unwrap();
694            while let Some(&(index, c)) = iter.peek() {
695                if let Some(i) = c.to_char_lossy().to_digit(10) {
696                    num = num
697                        .checked_mul(10)
698                        .and_then(|num| num.checked_add(i as usize))
699                        .filter(|&num| num <= max_value)
700                        .ok_or((too_big, index))?;
701                    iter.next().unwrap();
702                } else {
703                    break;
704                }
705            }
706            return Ok(Some(CFormatQuantity::Amount(num)));
707        }
708    }
709    Ok(None)
710}
711
712fn parse_precision<C, I>(iter: &mut ParseIter<I>) -> Result<Option<CFormatPrecision>, ParsingError>
713where
714    C: FormatChar,
715    I: Iterator<Item = C>,
716{
717    if iter.next_if(|(_, c)| c.eq_char('.')).is_some() {
718        let quantity = parse_quantity(iter, i32::MAX as usize, CFormatErrorType::PrecisionTooBig)?;
719        let precision = quantity.map_or(CFormatPrecision::Dot, CFormatPrecision::Quantity);
720        return Ok(Some(precision));
721    }
722    Ok(None)
723}
724
725fn parse_text_inside_parentheses<T, I>(iter: &mut ParseIter<I>) -> Option<T>
726where
727    T: FormatBuf,
728    I: Iterator<Item = T::Char>,
729{
730    let mut counter: i32 = 1;
731    let mut contained_text = T::default();
732    loop {
733        let (_, c) = iter.next()?;
734        match c.to_char_lossy() {
735            '(' => {
736                counter += 1;
737            }
738            ')' => {
739                counter -= 1;
740            }
741            _ => (),
742        }
743
744        if counter > 0 {
745            contained_text.extend([c]);
746        } else {
747            break;
748        }
749    }
750
751    Some(contained_text)
752}
753
754#[derive(Debug, Eq, PartialEq)]
755pub enum CFormatPart<T> {
756    Literal(T),
757    Spec(CFormatSpecKeyed<T>),
758}
759
760impl<T> CFormatPart<T> {
761    #[inline]
762    pub const fn is_specifier(&self) -> bool {
763        matches!(self, Self::Spec { .. })
764    }
765
766    #[inline]
767    pub const fn has_key(&self) -> bool {
768        match self {
769            Self::Spec(s) => s.mapping_key.is_some(),
770            _ => false,
771        }
772    }
773}
774
775#[derive(Debug, Eq, PartialEq)]
776pub struct CFormatStrOrBytes<S> {
777    parts: Vec<(usize, CFormatPart<S>)>,
778}
779
780impl<S> CFormatStrOrBytes<S> {
781    #[must_use]
782    pub fn check_specifiers(&self) -> Option<(usize, bool)> {
783        let mut count = 0;
784        let mut mapping_required = false;
785        for (_, part) in &self.parts {
786            if part.is_specifier() {
787                let has_key = part.has_key();
788                if count == 0 {
789                    mapping_required = has_key;
790                } else if mapping_required != has_key {
791                    return None;
792                }
793                count += 1;
794            }
795        }
796        Some((count, mapping_required))
797    }
798
799    #[inline]
800    pub fn iter(&self) -> impl Iterator<Item = &(usize, CFormatPart<S>)> {
801        self.parts.iter()
802    }
803
804    #[inline]
805    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut (usize, CFormatPart<S>)> {
806        self.parts.iter_mut()
807    }
808
809    pub fn parse<I>(iter: &mut ParseIter<I>, context: CFormatContext) -> Result<Self, CFormatError>
810    where
811        S: FormatBuf,
812        I: Iterator<Item = S::Char>,
813    {
814        let mut parts = vec![];
815        let mut literal = S::default();
816        let mut part_index = 0;
817        while let Some((index, c)) = iter.next() {
818            if c.eq_char('%') {
819                if let Some(&(_, second)) = iter.peek() {
820                    if second.eq_char('%') {
821                        iter.next().unwrap();
822                        literal.extend([second]);
823                        continue;
824                    }
825
826                    if !literal.is_empty() {
827                        parts.push((
828                            part_index,
829                            CFormatPart::Literal(core::mem::take(&mut literal)),
830                        ));
831                    }
832
833                    let spec =
834                        CFormatSpecKeyed::parse(iter, context).map_err(|err| CFormatError {
835                            typ: err.0,
836                            index: err.1,
837                        })?;
838
839                    parts.push((index, CFormatPart::Spec(spec)));
840                    if let Some(&(index, _)) = iter.peek() {
841                        part_index = index;
842                    }
843                } else {
844                    return Err(CFormatError {
845                        typ: CFormatErrorType::IncompleteFormat,
846                        index: index + 1,
847                    });
848                }
849            } else {
850                literal.extend([c]);
851            }
852        }
853        if !literal.is_empty() {
854            parts.push((part_index, CFormatPart::Literal(literal)));
855        }
856        Ok(Self { parts })
857    }
858}
859
860impl<S> IntoIterator for CFormatStrOrBytes<S> {
861    type Item = (usize, CFormatPart<S>);
862    type IntoIter = alloc::vec::IntoIter<Self::Item>;
863
864    fn into_iter(self) -> Self::IntoIter {
865        self.parts.into_iter()
866    }
867}
868
869pub type CFormatBytes = CFormatStrOrBytes<Vec<u8>>;
870
871impl CFormatBytes {
872    pub fn parse_from_bytes(bytes: &[u8]) -> Result<Self, CFormatError> {
873        let mut iter = bytes.iter().copied().enumerate().peekable();
874        Self::parse(&mut iter, CFormatContext::Bytes)
875    }
876}
877
878pub type CFormatString = CFormatStrOrBytes<String>;
879
880impl FromStr for CFormatString {
881    type Err = CFormatError;
882
883    fn from_str(text: &str) -> Result<Self, Self::Err> {
884        let mut iter = text.chars().enumerate().peekable();
885        Self::parse(&mut iter, CFormatContext::Str)
886    }
887}
888
889pub type CFormatWtf8 = CFormatStrOrBytes<Wtf8Buf>;
890
891impl CFormatWtf8 {
892    pub fn parse_from_wtf8(s: &Wtf8) -> Result<Self, CFormatError> {
893        let mut iter = s.code_points().enumerate().peekable();
894        Self::parse(&mut iter, CFormatContext::Str)
895    }
896}
897
898#[cfg(test)]
899mod tests {
900    use super::*;
901
902    #[test]
903    fn fill_and_align() {
904        assert_eq!(
905            "%10s"
906                .parse::<CFormatSpec>()
907                .unwrap()
908                .format_string("test".to_owned()),
909            "      test".to_owned()
910        );
911        assert_eq!(
912            "%-10s"
913                .parse::<CFormatSpec>()
914                .unwrap()
915                .format_string("test".to_owned()),
916            "test      ".to_owned()
917        );
918        assert_eq!(
919            "%#10x"
920                .parse::<CFormatSpec>()
921                .unwrap()
922                .format_number(&BigInt::from(0x1337)),
923            "    0x1337".to_owned()
924        );
925        assert_eq!(
926            "%-#10x"
927                .parse::<CFormatSpec>()
928                .unwrap()
929                .format_number(&BigInt::from(0x1337)),
930            "0x1337    ".to_owned()
931        );
932    }
933
934    #[test]
935    fn parse_key() {
936        let expected = Ok(CFormatSpecKeyed {
937            mapping_key: Some("amount".to_owned()),
938            spec: CFormatSpec {
939                format_type: CFormatType::Number(CNumberType::DecimalD),
940                min_field_width: None,
941                precision: None,
942                flags: CConversionFlags::empty(),
943            },
944        });
945        assert_eq!("%(amount)d".parse::<CFormatSpecKeyed<String>>(), expected);
946
947        let expected = Ok(CFormatSpecKeyed {
948            mapping_key: Some("m((u(((l((((ti))))p)))l))e".to_owned()),
949            spec: CFormatSpec {
950                format_type: CFormatType::Number(CNumberType::DecimalD),
951                min_field_width: None,
952                precision: None,
953                flags: CConversionFlags::empty(),
954            },
955        });
956        assert_eq!(
957            "%(m((u(((l((((ti))))p)))l))e)d".parse::<CFormatSpecKeyed<String>>(),
958            expected
959        );
960    }
961
962    #[test]
963    fn format_parse_key_fail() {
964        assert_eq!(
965            "%(aged".parse::<CFormatString>(),
966            Err(CFormatError {
967                typ: CFormatErrorType::UnmatchedKeyParentheses,
968                index: 1
969            })
970        );
971    }
972
973    #[test]
974    fn format_parse_type_fail() {
975        let parsed = "Hello %n".parse::<CFormatString>().unwrap();
976        let spec = parsed.iter().find_map(|(_, part)| match part {
977            CFormatPart::Spec(spec) => Some(&spec.spec),
978            CFormatPart::Literal(_) => None,
979        });
980        assert!(
981            matches!(
982                spec.map(|s| &s.format_type),
983                Some(CFormatType::Unsupported { ch, index: 7 }) if *ch == 'n'
984            ),
985            "{spec:?}"
986        );
987    }
988
989    #[test]
990    fn incomplete_format_fail() {
991        assert_eq!(
992            "Hello %".parse::<CFormatString>(),
993            Err(CFormatError {
994                typ: CFormatErrorType::IncompleteFormat,
995                index: 7
996            })
997        );
998    }
999
1000    #[test]
1001    fn width_and_precision_have_distinct_limits_and_errors() {
1002        let precision = "%.2147483648f".parse::<CFormatSpec>().unwrap_err();
1003        assert_eq!(precision.0, CFormatErrorType::PrecisionTooBig);
1004        assert_eq!(
1005            CFormatError {
1006                typ: precision.0,
1007                index: precision.1,
1008            }
1009            .to_string(),
1010            "precision too big"
1011        );
1012
1013        let oversized_width = format!("%{}f", isize::MAX as u128 + 1);
1014        let width = oversized_width.parse::<CFormatSpec>().unwrap_err();
1015        assert_eq!(width.0, CFormatErrorType::WidthTooBig);
1016        assert_eq!(
1017            CFormatError {
1018                typ: width.0,
1019                index: width.1,
1020            }
1021            .to_string(),
1022            "width too big"
1023        );
1024
1025        if usize::BITS > 32 {
1026            let spec = "%2147483648f".parse::<CFormatSpec>().unwrap();
1027            assert_eq!(
1028                spec.min_field_width,
1029                Some(CFormatQuantity::Amount(2_147_483_648))
1030            );
1031        }
1032    }
1033
1034    #[test]
1035    fn parse_flags() {
1036        let expected = Ok(CFormatSpec {
1037            format_type: CFormatType::Number(CNumberType::DecimalD),
1038            min_field_width: Some(CFormatQuantity::Amount(10)),
1039            precision: None,
1040            flags: CConversionFlags::all(),
1041        });
1042        let parsed = "%  0   -+++###10d".parse::<CFormatSpec>();
1043        assert_eq!(parsed, expected);
1044        assert_eq!(
1045            parsed.unwrap().format_number(&BigInt::from(12)),
1046            "+12       ".to_owned()
1047        );
1048    }
1049
1050    #[test]
1051    fn parse_and_format_string() {
1052        assert_eq!(
1053            "%5.4s"
1054                .parse::<CFormatSpec>()
1055                .unwrap()
1056                .format_string("Hello, World!".to_owned()),
1057            " Hell".to_owned()
1058        );
1059        assert_eq!(
1060            "%-5.4s"
1061                .parse::<CFormatSpec>()
1062                .unwrap()
1063                .format_string("Hello, World!".to_owned()),
1064            "Hell ".to_owned()
1065        );
1066        assert_eq!(
1067            "%.s"
1068                .parse::<CFormatSpec>()
1069                .unwrap()
1070                .format_string("Hello, World!".to_owned()),
1071            "".to_owned()
1072        );
1073        assert_eq!(
1074            "%5.s"
1075                .parse::<CFormatSpec>()
1076                .unwrap()
1077                .format_string("Hello, World!".to_owned()),
1078            "     ".to_owned()
1079        );
1080    }
1081
1082    #[test]
1083    fn parse_and_format_unicode_string() {
1084        assert_eq!(
1085            "%.2s"
1086                .parse::<CFormatSpec>()
1087                .unwrap()
1088                .format_string("❤❤❤❤❤❤❤❤".to_owned()),
1089            "❤❤".to_owned()
1090        );
1091    }
1092
1093    #[test]
1094    fn parse_and_format_number() {
1095        assert_eq!(
1096            "%5d"
1097                .parse::<CFormatSpec>()
1098                .unwrap()
1099                .format_number(&BigInt::from(27)),
1100            "   27".to_owned()
1101        );
1102        assert_eq!(
1103            "%05d"
1104                .parse::<CFormatSpec>()
1105                .unwrap()
1106                .format_number(&BigInt::from(27)),
1107            "00027".to_owned()
1108        );
1109        assert_eq!(
1110            "%.5d"
1111                .parse::<CFormatSpec>()
1112                .unwrap()
1113                .format_number(&BigInt::from(27)),
1114            "00027".to_owned()
1115        );
1116        assert_eq!(
1117            "%+05d"
1118                .parse::<CFormatSpec>()
1119                .unwrap()
1120                .format_number(&BigInt::from(27)),
1121            "+0027".to_owned()
1122        );
1123        assert_eq!(
1124            "%-d"
1125                .parse::<CFormatSpec>()
1126                .unwrap()
1127                .format_number(&BigInt::from(-27)),
1128            "-27".to_owned()
1129        );
1130        assert_eq!(
1131            "% d"
1132                .parse::<CFormatSpec>()
1133                .unwrap()
1134                .format_number(&BigInt::from(27)),
1135            " 27".to_owned()
1136        );
1137        assert_eq!(
1138            "% d"
1139                .parse::<CFormatSpec>()
1140                .unwrap()
1141                .format_number(&BigInt::from(-27)),
1142            "-27".to_owned()
1143        );
1144        assert_eq!(
1145            "%08x"
1146                .parse::<CFormatSpec>()
1147                .unwrap()
1148                .format_number(&BigInt::from(0x1337)),
1149            "00001337".to_owned()
1150        );
1151        assert_eq!(
1152            "%#010x"
1153                .parse::<CFormatSpec>()
1154                .unwrap()
1155                .format_number(&BigInt::from(0x1337)),
1156            "0x00001337".to_owned()
1157        );
1158        assert_eq!(
1159            "%-#010x"
1160                .parse::<CFormatSpec>()
1161                .unwrap()
1162                .format_number(&BigInt::from(0x1337)),
1163            "0x1337    ".to_owned()
1164        );
1165    }
1166
1167    #[test]
1168    fn parse_and_format_float() {
1169        assert_eq!(
1170            "%f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
1171            "1.234500"
1172        );
1173        assert_eq!(
1174            "%.2f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
1175            "1.23"
1176        );
1177        assert_eq!(
1178            "%.f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
1179            "1"
1180        );
1181        assert_eq!(
1182            "%+.f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
1183            "+1"
1184        );
1185        assert_eq!(
1186            "%+f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
1187            "+1.234500"
1188        );
1189        assert_eq!(
1190            "% f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
1191            " 1.234500"
1192        );
1193        assert_eq!(
1194            "%f".parse::<CFormatSpec>().unwrap().format_float(-1.2345),
1195            "-1.234500"
1196        );
1197        assert_eq!(
1198            "%f".parse::<CFormatSpec>()
1199                .unwrap()
1200                .format_float(1.2345678901),
1201            "1.234568"
1202        );
1203    }
1204
1205    #[test]
1206    fn format_parse() {
1207        let fmt = "Hello, my name is %s and I'm %d years old";
1208        let expected = Ok(CFormatString {
1209            parts: vec![
1210                (0, CFormatPart::Literal("Hello, my name is ".to_owned())),
1211                (
1212                    18,
1213                    CFormatPart::Spec(CFormatSpecKeyed {
1214                        mapping_key: None,
1215                        spec: CFormatSpec {
1216                            format_type: CFormatType::String(CFormatConversion::Str),
1217                            min_field_width: None,
1218                            precision: None,
1219                            flags: CConversionFlags::empty(),
1220                        },
1221                    }),
1222                ),
1223                (20, CFormatPart::Literal(" and I'm ".to_owned())),
1224                (
1225                    29,
1226                    CFormatPart::Spec(CFormatSpecKeyed {
1227                        mapping_key: None,
1228                        spec: CFormatSpec {
1229                            format_type: CFormatType::Number(CNumberType::DecimalD),
1230                            min_field_width: None,
1231                            precision: None,
1232                            flags: CConversionFlags::empty(),
1233                        },
1234                    }),
1235                ),
1236                (31, CFormatPart::Literal(" years old".to_owned())),
1237            ],
1238        });
1239        let result = fmt.parse::<CFormatString>();
1240        assert_eq!(
1241            result, expected,
1242            "left = {result:#?} \n\n\n right = {expected:#?}"
1243        );
1244    }
1245}