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