1use 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#[derive(Clone, Debug)]
19pub struct LocaleInfo {
20 pub thousands_sep: String,
21 pub decimal_point: String,
22 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)]
35#[repr(u8)]
36pub enum FormatConversion {
37 Str = b's',
38 Repr = b'r',
39 Ascii = b'b',
40 Bytes = b'a',
41}
42
43impl FormatParse for FormatConversion {
44 fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
45 let Some(conversion) = Self::from_string(text) else {
46 return (None, text);
47 };
48 let mut chars = text.code_points();
49 chars.next(); chars.next(); (Some(conversion), chars.as_wtf8())
52 }
53}
54
55impl FormatConversion {
56 pub fn from_char(c: CodePoint) -> Option<Self> {
57 match c.to_char_lossy() {
58 's' => Some(Self::Str),
59 'r' => Some(Self::Repr),
60 'a' => Some(Self::Ascii),
61 'b' => Some(Self::Bytes),
62 _ => None,
63 }
64 }
65
66 fn from_string(text: &Wtf8) -> Option<Self> {
67 let mut chars = text.code_points();
68 if chars.next()? != '!' {
69 return None;
70 }
71
72 Self::from_char(chars.next()?)
73 }
74}
75
76#[derive(Debug, Copy, Clone, PartialEq)]
77pub enum FormatAlign {
78 Left,
79 Right,
80 AfterSign,
81 Center,
82}
83
84impl FormatAlign {
85 fn from_char(c: CodePoint) -> Option<Self> {
86 match c.to_char_lossy() {
87 '<' => Some(Self::Left),
88 '>' => Some(Self::Right),
89 '=' => Some(Self::AfterSign),
90 '^' => Some(Self::Center),
91 _ => None,
92 }
93 }
94}
95
96impl FormatParse for FormatAlign {
97 fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
98 let mut chars = text.code_points();
99 if let Some(maybe_align) = chars.next().and_then(Self::from_char) {
100 (Some(maybe_align), chars.as_wtf8())
101 } else {
102 (None, text)
103 }
104 }
105}
106
107#[derive(Debug, Copy, Clone, PartialEq)]
108pub enum FormatSign {
109 Plus,
110 Minus,
111 MinusOrSpace,
112}
113
114impl FormatParse for FormatSign {
115 fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
116 let mut chars = text.code_points();
117 match chars.next().and_then(CodePoint::to_char) {
118 Some('-') => (Some(Self::Minus), chars.as_wtf8()),
119 Some('+') => (Some(Self::Plus), chars.as_wtf8()),
120 Some(' ') => (Some(Self::MinusOrSpace), chars.as_wtf8()),
121 _ => (None, text),
122 }
123 }
124}
125
126#[derive(Clone, Copy, Debug, PartialEq)]
127pub enum FormatGrouping {
128 Comma,
129 Underscore,
130}
131
132impl FormatParse for FormatGrouping {
133 fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
134 let mut chars = text.code_points();
135 match chars.next().and_then(CodePoint::to_char) {
136 Some('_') => (Some(Self::Underscore), chars.as_wtf8()),
137 Some(',') => (Some(Self::Comma), chars.as_wtf8()),
138 _ => (None, text),
139 }
140 }
141}
142
143impl From<&FormatGrouping> for char {
144 fn from(fg: &FormatGrouping) -> Self {
145 match fg {
146 FormatGrouping::Comma => ',',
147 FormatGrouping::Underscore => '_',
148 }
149 }
150}
151
152#[derive(Clone, Copy, Debug, PartialEq)]
153pub enum FormatType {
154 String,
155 Binary,
156 Character,
157 Decimal,
158 Octal,
159 Number(Case),
160 Hex(Case),
161 Exponent(Case),
162 GeneralFormat(Case),
163 FixedPoint(Case),
164 Percentage,
165 Unknown(char),
166}
167
168impl From<&FormatType> for char {
169 fn from(from: &FormatType) -> Self {
170 match from {
171 FormatType::String => 's',
172 FormatType::Binary => 'b',
173 FormatType::Character => 'c',
174 FormatType::Decimal => 'd',
175 FormatType::Octal => 'o',
176 FormatType::Number(Case::Lower) => 'n',
177 FormatType::Number(Case::Upper) => 'N',
178 FormatType::Hex(Case::Lower) => 'x',
179 FormatType::Hex(Case::Upper) => 'X',
180 FormatType::Exponent(Case::Lower) => 'e',
181 FormatType::Exponent(Case::Upper) => 'E',
182 FormatType::GeneralFormat(Case::Lower) => 'g',
183 FormatType::GeneralFormat(Case::Upper) => 'G',
184 FormatType::FixedPoint(Case::Lower) => 'f',
185 FormatType::FixedPoint(Case::Upper) => 'F',
186 FormatType::Percentage => '%',
187 FormatType::Unknown(c) => *c,
188 }
189 }
190}
191
192impl FormatParse for FormatType {
193 fn parse(text: &Wtf8) -> (Option<Self>, &Wtf8) {
194 let mut chars = text.code_points();
195 match chars.next().and_then(CodePoint::to_char) {
196 Some('s') => (Some(Self::String), chars.as_wtf8()),
197 Some('b') => (Some(Self::Binary), chars.as_wtf8()),
198 Some('c') => (Some(Self::Character), chars.as_wtf8()),
199 Some('d') => (Some(Self::Decimal), chars.as_wtf8()),
200 Some('o') => (Some(Self::Octal), chars.as_wtf8()),
201 Some('n') => (Some(Self::Number(Case::Lower)), chars.as_wtf8()),
202 Some('N') => (Some(Self::Number(Case::Upper)), chars.as_wtf8()),
203 Some('x') => (Some(Self::Hex(Case::Lower)), chars.as_wtf8()),
204 Some('X') => (Some(Self::Hex(Case::Upper)), chars.as_wtf8()),
205 Some('e') => (Some(Self::Exponent(Case::Lower)), chars.as_wtf8()),
206 Some('E') => (Some(Self::Exponent(Case::Upper)), chars.as_wtf8()),
207 Some('f') => (Some(Self::FixedPoint(Case::Lower)), chars.as_wtf8()),
208 Some('F') => (Some(Self::FixedPoint(Case::Upper)), chars.as_wtf8()),
209 Some('g') => (Some(Self::GeneralFormat(Case::Lower)), chars.as_wtf8()),
210 Some('G') => (Some(Self::GeneralFormat(Case::Upper)), chars.as_wtf8()),
211 Some('%') => (Some(Self::Percentage), chars.as_wtf8()),
212 Some(c) => (Some(Self::Unknown(c)), chars.as_wtf8()),
213 _ => (None, text),
214 }
215 }
216}
217
218#[derive(Clone, Copy, Debug, PartialEq)]
219pub struct FormatSpec {
220 conversion: Option<FormatConversion>,
221 fill: Option<CodePoint>,
222 align: Option<FormatAlign>,
223 sign: Option<FormatSign>,
224 alternate_form: bool,
225 width: Option<usize>,
226 grouping_option: Option<FormatGrouping>,
227 precision: Option<usize>,
228 format_type: Option<FormatType>,
229}
230
231fn get_num_digits(text: &Wtf8) -> usize {
232 for (index, character) in text.code_point_indices() {
233 if !character.is_char_and(|c| c.is_ascii_digit()) {
234 return index;
235 }
236 }
237 text.len()
238}
239
240fn parse_fill_and_align(text: &Wtf8) -> (Option<CodePoint>, Option<FormatAlign>, &Wtf8) {
241 let char_indices: Vec<(usize, CodePoint)> = text.code_point_indices().take(3).collect();
242 if char_indices.is_empty() {
243 (None, None, text)
244 } else if char_indices.len() == 1 {
245 let (maybe_align, remaining) = FormatAlign::parse(text);
246 (None, maybe_align, remaining)
247 } else {
248 let (maybe_align, remaining) = FormatAlign::parse(&text[char_indices[1].0..]);
249 if maybe_align.is_some() {
250 (Some(char_indices[0].1), maybe_align, remaining)
251 } else {
252 let (only_align, only_align_remaining) = FormatAlign::parse(text);
253 (None, only_align, only_align_remaining)
254 }
255 }
256}
257
258fn parse_number(text: &Wtf8) -> Result<(Option<usize>, &Wtf8), FormatSpecError> {
259 let num_digits: usize = get_num_digits(text);
260 if num_digits == 0 {
261 return Ok((None, text));
262 }
263 if let Some(num) = parse_usize(&text[..num_digits]) {
264 Ok((Some(num), &text[num_digits..]))
265 } else {
266 Err(FormatSpecError::DecimalDigitsTooMany)
268 }
269}
270
271fn parse_alternate_form(text: &Wtf8) -> (bool, &Wtf8) {
272 let mut chars = text.code_points();
273 match chars.next().and_then(CodePoint::to_char) {
274 Some('#') => (true, chars.as_wtf8()),
275 _ => (false, text),
276 }
277}
278
279fn parse_zero(text: &Wtf8) -> (bool, &Wtf8) {
280 let mut chars = text.code_points();
281 match chars.next().and_then(CodePoint::to_char) {
282 Some('0') => (true, chars.as_wtf8()),
283 _ => (false, text),
284 }
285}
286
287fn parse_precision(text: &Wtf8) -> Result<(Option<usize>, &Wtf8), FormatSpecError> {
288 let mut chars = text.code_points();
289 Ok(match chars.next().and_then(CodePoint::to_char) {
290 Some('.') => {
291 let (size, remaining) = parse_number(chars.as_wtf8())?;
292 if let Some(size) = size {
293 if size > i32::MAX as usize {
294 return Err(FormatSpecError::PrecisionTooBig);
295 }
296 (Some(size), remaining)
297 } else {
298 (None, text)
299 }
300 }
301 _ => (None, text),
302 })
303}
304
305impl FormatSpec {
306 pub fn parse(text: impl AsRef<Wtf8>) -> Result<Self, FormatSpecError> {
307 Self::_parse(text.as_ref())
308 }
309
310 fn _parse(text: &Wtf8) -> Result<Self, FormatSpecError> {
311 let (conversion, text) = FormatConversion::parse(text);
313 let (mut fill, mut align, text) = parse_fill_and_align(text);
314 let (sign, text) = FormatSign::parse(text);
315 let (alternate_form, text) = parse_alternate_form(text);
316 let (zero, text) = parse_zero(text);
317 let (width, text) = parse_number(text)?;
318 let (grouping_option, text) = FormatGrouping::parse(text);
319 if let Some(grouping) = &grouping_option {
320 Self::validate_separator(grouping, text)?;
321 }
322 let (precision, text) = parse_precision(text)?;
323 let (format_type, text) = FormatType::parse(text);
324 if !text.is_empty() {
325 return Err(FormatSpecError::InvalidFormatSpecifier);
326 }
327
328 if zero && fill.is_none() {
329 fill.replace('0'.into());
330 align = align.or(Some(FormatAlign::AfterSign));
331 }
332
333 Ok(Self {
334 conversion,
335 fill,
336 align,
337 sign,
338 alternate_form,
339 width,
340 grouping_option,
341 precision,
342 format_type,
343 })
344 }
345
346 fn validate_separator(grouping: &FormatGrouping, text: &Wtf8) -> Result<(), FormatSpecError> {
347 let mut chars = text.code_points().peekable();
348 match chars.peek().and_then(|cp| CodePoint::to_char(*cp)) {
349 Some(c) if c == ',' || c == '_' => {
350 if c == char::from(grouping) {
351 Err(FormatSpecError::UnspecifiedFormat(c, c))
352 } else {
353 Err(FormatSpecError::ExclusiveFormat(',', '_'))
354 }
355 }
356 _ => Ok(()),
357 }
358 }
359
360 fn compute_fill_string(fill_char: CodePoint, fill_chars_needed: i32) -> Wtf8Buf {
361 (0..fill_chars_needed).map(|_| fill_char).collect()
362 }
363
364 fn add_magnitude_separators_for_char(
365 magnitude_str: String,
366 inter: i32,
367 sep: char,
368 disp_digit_cnt: i32,
369 ) -> String {
370 let mut parts = magnitude_str.splitn(2, '.');
372 let magnitude_int_str = parts.next().unwrap().to_string();
373 let dec_digit_cnt = magnitude_str.len() as i32 - magnitude_int_str.len() as i32;
374 let int_digit_cnt = disp_digit_cnt - dec_digit_cnt;
375 let mut result = Self::separate_integer(magnitude_int_str, inter, sep, int_digit_cnt);
376 if let Some(part) = parts.next() {
377 result.push_str(&format!(".{part}"))
378 }
379 result
380 }
381
382 fn separate_integer(
383 magnitude_str: String,
384 inter: i32,
385 sep: char,
386 disp_digit_cnt: i32,
387 ) -> String {
388 let magnitude_len = magnitude_str.len() as i32;
389 let offset = (disp_digit_cnt % (inter + 1) == 0) as i32;
390 let disp_digit_cnt = disp_digit_cnt + offset;
391 let pad_cnt = disp_digit_cnt - magnitude_len;
392 let sep_cnt = disp_digit_cnt / (inter + 1);
393 let diff = pad_cnt - sep_cnt;
394 if pad_cnt > 0 && diff > 0 {
395 let padding = "0".repeat(diff as usize);
397 let padded_num = format!("{padding}{magnitude_str}");
398 Self::insert_separator(padded_num, inter, sep, sep_cnt)
399 } else {
400 let sep_cnt = (magnitude_len - 1) / inter;
402 Self::insert_separator(magnitude_str, inter, sep, sep_cnt)
403 }
404 }
405
406 fn insert_separator(mut magnitude_str: String, inter: i32, sep: char, sep_cnt: i32) -> String {
407 let magnitude_len = magnitude_str.len() as i32;
408 for i in 1..=sep_cnt {
409 magnitude_str.insert((magnitude_len - inter * i) as usize, sep);
410 }
411 magnitude_str
412 }
413
414 fn validate_format(&self, default_format_type: FormatType) -> Result<(), FormatSpecError> {
415 let format_type = self.format_type.as_ref().unwrap_or(&default_format_type);
416 match (&self.grouping_option, format_type) {
417 (
418 Some(FormatGrouping::Comma),
419 FormatType::String
420 | FormatType::Character
421 | FormatType::Binary
422 | FormatType::Octal
423 | FormatType::Hex(_)
424 | FormatType::Number(_),
425 ) => {
426 let ch = char::from(format_type);
427 Err(FormatSpecError::UnspecifiedFormat(',', ch))
428 }
429 (
430 Some(FormatGrouping::Underscore),
431 FormatType::String | FormatType::Character | FormatType::Number(_),
432 ) => {
433 let ch = char::from(format_type);
434 Err(FormatSpecError::UnspecifiedFormat('_', ch))
435 }
436 _ => Ok(()),
437 }
438 }
439
440 const fn get_separator_interval(&self) -> usize {
441 match self.format_type {
442 Some(FormatType::Binary | FormatType::Octal | FormatType::Hex(_)) => 4,
443 Some(
444 FormatType::Decimal
445 | FormatType::FixedPoint(_)
446 | FormatType::GeneralFormat(_)
447 | FormatType::Exponent(_)
448 | FormatType::Percentage
449 | FormatType::Number(_),
450 ) => 3,
451 None => 3,
452 _ => panic!("Separators only valid for numbers!"),
453 }
454 }
455
456 fn add_magnitude_separators(&self, magnitude_str: String, prefix: &str) -> String {
457 match &self.grouping_option {
458 Some(fg) => {
459 let sep = char::from(fg);
460 let inter = self.get_separator_interval().try_into().unwrap();
461 let magnitude_len = magnitude_str.len();
462 let disp_digit_cnt = if self.fill == Some('0'.into())
463 && self.align == Some(FormatAlign::AfterSign)
464 {
465 let width = self.width.unwrap_or(magnitude_len) as i32 - prefix.len() as i32;
466 cmp::max(width, magnitude_len as i32)
467 } else {
468 magnitude_len as i32
469 };
470 Self::add_magnitude_separators_for_char(magnitude_str, inter, sep, disp_digit_cnt)
471 }
472 None => magnitude_str,
473 }
474 }
475
476 pub fn has_locale_format(&self) -> bool {
478 matches!(self.format_type, Some(FormatType::Number(Case::Lower)))
479 }
480
481 fn insert_locale_grouping(int_part: &str, locale: &LocaleInfo) -> String {
484 if locale.grouping.is_empty() || locale.thousands_sep.is_empty() || int_part.len() <= 1 {
485 return int_part.to_string();
486 }
487
488 let mut group_idx = 0;
489 let mut group_size = locale.grouping[0] as usize;
490
491 if group_size == 0 {
492 return int_part.to_string();
493 }
494
495 let len = int_part.len();
497 let mut groups: Vec<&str> = Vec::new();
498 let mut pos = len;
499
500 loop {
501 if pos <= group_size {
502 groups.push(&int_part[..pos]);
503 break;
504 }
505
506 groups.push(&int_part[pos - group_size..pos]);
507 pos -= group_size;
508
509 if group_idx + 1 < locale.grouping.len() {
511 let next = locale.grouping[group_idx + 1] as usize;
512 if next != 0 {
513 group_size = next;
514 group_idx += 1;
515 }
516 }
518 }
519
520 groups.reverse();
522 groups.join(&locale.thousands_sep)
523 }
524
525 fn apply_locale_formatting(magnitude_str: String, locale: &LocaleInfo) -> String {
527 let mut parts = magnitude_str.splitn(2, '.');
528 let int_part = parts.next().unwrap();
529 let grouped = Self::insert_locale_grouping(int_part, locale);
530
531 if let Some(frac_part) = parts.next() {
532 format!("{grouped}{}{frac_part}", locale.decimal_point)
533 } else {
534 grouped
535 }
536 }
537
538 pub fn format_int_locale(
540 &self,
541 num: &BigInt,
542 locale: &LocaleInfo,
543 ) -> Result<String, FormatSpecError> {
544 self.validate_format(FormatType::Decimal)?;
545 let magnitude = num.abs();
546
547 let raw_magnitude_str = match self.format_type {
548 Some(FormatType::Number(Case::Lower)) => self.format_int_radix(magnitude, 10),
549 _ => return self.format_int(num),
550 }?;
551
552 let magnitude_str = Self::apply_locale_formatting(raw_magnitude_str, locale);
553
554 let format_sign = self.sign.unwrap_or(FormatSign::Minus);
555 let sign_str = match num.sign() {
556 Sign::Minus => "-",
557 _ => match format_sign {
558 FormatSign::Plus => "+",
559 FormatSign::Minus => "",
560 FormatSign::MinusOrSpace => " ",
561 },
562 };
563
564 self.format_sign_and_align(&AsciiStr::new(&magnitude_str), sign_str, FormatAlign::Right)
565 }
566
567 pub fn format_float_locale(
569 &self,
570 num: f64,
571 locale: &LocaleInfo,
572 ) -> Result<String, FormatSpecError> {
573 self.validate_format(FormatType::FixedPoint(Case::Lower))?;
574 let precision = self.precision.unwrap_or(6);
575 let magnitude = num.abs();
576
577 let raw_magnitude_str = match &self.format_type {
578 Some(FormatType::Number(case)) => {
579 let precision = if precision == 0 { 1 } else { precision };
580 Ok(float::format_general(
581 precision,
582 magnitude,
583 *case,
584 self.alternate_form,
585 false,
586 ))
587 }
588 _ => return self.format_float(num),
589 }?;
590
591 let magnitude_str = Self::apply_locale_formatting(raw_magnitude_str, locale);
592
593 let format_sign = self.sign.unwrap_or(FormatSign::Minus);
594 let sign_str = if num.is_sign_negative() && !num.is_nan() {
595 "-"
596 } else {
597 match format_sign {
598 FormatSign::Plus => "+",
599 FormatSign::Minus => "",
600 FormatSign::MinusOrSpace => " ",
601 }
602 };
603
604 self.format_sign_and_align(&AsciiStr::new(&magnitude_str), sign_str, FormatAlign::Right)
605 }
606
607 pub fn format_complex_locale(
609 &self,
610 num: &Complex64,
611 locale: &LocaleInfo,
612 ) -> Result<String, FormatSpecError> {
613 let locale_spec = FormatSpec {
617 format_type: Some(FormatType::GeneralFormat(Case::Lower)),
618 ..*self
619 };
620 let (formatted_re, formatted_im) = locale_spec.format_complex_re_im(num)?;
621
622 let grouped_re = if formatted_re.is_empty() {
624 formatted_re
625 } else {
626 let (sign, mag) = if formatted_re.starts_with('-')
628 || formatted_re.starts_with('+')
629 || formatted_re.starts_with(' ')
630 {
631 formatted_re.split_at(1)
632 } else {
633 ("", formatted_re.as_str())
634 };
635 format!(
636 "{sign}{}",
637 Self::apply_locale_formatting(mag.to_string(), locale)
638 )
639 };
640
641 let im_str = &formatted_im;
644 let (im_sign, im_rest) = if im_str.starts_with('+') || im_str.starts_with('-') {
645 im_str.split_at(1)
646 } else {
647 ("", im_str.as_str())
648 };
649 let im_mag = im_rest.strip_suffix('j').unwrap_or(im_rest);
650 let im_grouped = Self::apply_locale_formatting(im_mag.to_string(), locale);
651 let grouped_im = format!("{im_sign}{im_grouped}j");
652
653 let magnitude_str = format!("{grouped_re}{grouped_im}");
655
656 self.format_sign_and_align(&AsciiStr::new(&magnitude_str), "", FormatAlign::Right)
657 }
658
659 pub fn format_bool(&self, input: bool) -> Result<String, FormatSpecError> {
660 let x = u8::from(input);
661 match &self.format_type {
662 Some(
663 FormatType::Binary
664 | FormatType::Decimal
665 | FormatType::Octal
666 | FormatType::Number(Case::Lower)
667 | FormatType::Hex(_)
668 | FormatType::GeneralFormat(_)
669 | FormatType::Character,
670 ) => self.format_int(&BigInt::from_u8(x).unwrap()),
671 Some(FormatType::Exponent(_) | FormatType::FixedPoint(_) | FormatType::Percentage) => {
672 self.format_float(x as f64)
673 }
674 None => {
675 let first_letter = (input.to_string().as_bytes()[0] as char).to_uppercase();
676 Ok(first_letter.collect::<String>() + &input.to_string()[1..])
677 }
678 Some(FormatType::Unknown(c)) => Err(FormatSpecError::UnknownFormatCode(*c, "int")),
679 _ => Err(FormatSpecError::InvalidFormatSpecifier),
680 }
681 }
682
683 pub fn format_float(&self, num: f64) -> Result<String, FormatSpecError> {
684 self.validate_format(FormatType::FixedPoint(Case::Lower))?;
685 let precision = self.precision.unwrap_or(6);
686 let magnitude = num.abs();
687 let raw_magnitude_str: Result<String, FormatSpecError> = match &self.format_type {
688 Some(FormatType::FixedPoint(case)) => Ok(float::format_fixed(
689 precision,
690 magnitude,
691 *case,
692 self.alternate_form,
693 )),
694 Some(FormatType::Decimal)
695 | Some(FormatType::Binary)
696 | Some(FormatType::Octal)
697 | Some(FormatType::Hex(_))
698 | Some(FormatType::String)
699 | Some(FormatType::Character)
700 | Some(FormatType::Number(Case::Upper))
701 | Some(FormatType::Unknown(_)) => {
702 let ch = char::from(self.format_type.as_ref().unwrap());
703 Err(FormatSpecError::UnknownFormatCode(ch, "float"))
704 }
705 Some(FormatType::GeneralFormat(case)) | Some(FormatType::Number(case)) => {
706 let precision = if precision == 0 { 1 } else { precision };
707 Ok(float::format_general(
708 precision,
709 magnitude,
710 *case,
711 self.alternate_form,
712 false,
713 ))
714 }
715 Some(FormatType::Exponent(case)) => Ok(float::format_exponent(
716 precision,
717 magnitude,
718 *case,
719 self.alternate_form,
720 )),
721 Some(FormatType::Percentage) => match magnitude {
722 magnitude if magnitude.is_nan() => Ok("nan%".to_owned()),
723 magnitude if magnitude.is_infinite() => Ok("inf%".to_owned()),
724 _ => {
725 let result = format!("{:.*}", precision, magnitude * 100.0);
726 let point = float::decimal_point_or_empty(precision, self.alternate_form);
727 Ok(format!("{result}{point}%"))
728 }
729 },
730 None => match magnitude {
731 magnitude if magnitude.is_nan() => Ok("nan".to_owned()),
732 magnitude if magnitude.is_infinite() => Ok("inf".to_owned()),
733 _ => match self.precision {
734 Some(precision) => Ok(float::format_general(
735 precision,
736 magnitude,
737 Case::Lower,
738 self.alternate_form,
739 true,
740 )),
741 None => Ok(float::to_string(magnitude)),
742 },
743 },
744 };
745 let format_sign = self.sign.unwrap_or(FormatSign::Minus);
746 let sign_str = if num.is_sign_negative() && !num.is_nan() {
747 "-"
748 } else {
749 match format_sign {
750 FormatSign::Plus => "+",
751 FormatSign::Minus => "",
752 FormatSign::MinusOrSpace => " ",
753 }
754 };
755 let magnitude_str = self.add_magnitude_separators(raw_magnitude_str?, sign_str);
756 self.format_sign_and_align(&AsciiStr::new(&magnitude_str), sign_str, FormatAlign::Right)
757 }
758
759 #[inline]
760 fn format_int_radix(&self, magnitude: BigInt, radix: u32) -> Result<String, FormatSpecError> {
761 match self.precision {
762 Some(_) => Err(FormatSpecError::PrecisionNotAllowed),
763 None => Ok(magnitude.to_str_radix(radix)),
764 }
765 }
766
767 pub fn format_int(&self, num: &BigInt) -> Result<String, FormatSpecError> {
768 self.validate_format(FormatType::Decimal)?;
769 let magnitude = num.abs();
770 let prefix = if self.alternate_form {
771 match self.format_type {
772 Some(FormatType::Binary) => "0b",
773 Some(FormatType::Octal) => "0o",
774 Some(FormatType::Hex(Case::Lower)) => "0x",
775 Some(FormatType::Hex(Case::Upper)) => "0X",
776 _ => "",
777 }
778 } else {
779 ""
780 };
781 let raw_magnitude_str = match self.format_type {
782 Some(FormatType::Binary) => self.format_int_radix(magnitude, 2),
783 Some(FormatType::Decimal) => self.format_int_radix(magnitude, 10),
784 Some(FormatType::Octal) => self.format_int_radix(magnitude, 8),
785 Some(FormatType::Hex(Case::Lower)) => self.format_int_radix(magnitude, 16),
786 Some(FormatType::Hex(Case::Upper)) => match self.precision {
787 Some(_) => Err(FormatSpecError::PrecisionNotAllowed),
788 None => {
789 let mut result = magnitude.to_str_radix(16);
790 result.make_ascii_uppercase();
791 Ok(result)
792 }
793 },
794 Some(FormatType::Number(Case::Lower)) => self.format_int_radix(magnitude, 10),
795 Some(FormatType::Number(Case::Upper)) => {
796 Err(FormatSpecError::UnknownFormatCode('N', "int"))
797 }
798 Some(FormatType::String) => Err(FormatSpecError::UnknownFormatCode('s', "int")),
799 Some(FormatType::Character) => match (self.sign, self.alternate_form) {
800 (Some(_), _) => Err(FormatSpecError::NotAllowed("Sign")),
801 (_, true) => Err(FormatSpecError::NotAllowed("Alternate form (#)")),
802 (_, _) => match num.to_u32() {
803 Some(n) if n <= 0x10ffff => Ok(core::char::from_u32(n).unwrap().to_string()),
804 Some(_) | None => Err(FormatSpecError::CodeNotInRange),
805 },
806 },
807 Some(FormatType::GeneralFormat(_))
808 | Some(FormatType::FixedPoint(_))
809 | Some(FormatType::Exponent(_))
810 | Some(FormatType::Percentage) => match num.to_f64() {
811 Some(float) => return self.format_float(float),
812 _ => Err(FormatSpecError::UnableToConvert),
813 },
814 Some(FormatType::Unknown(c)) => Err(FormatSpecError::UnknownFormatCode(c, "int")),
815 None => self.format_int_radix(magnitude, 10),
816 }?;
817 let format_sign = self.sign.unwrap_or(FormatSign::Minus);
818 let sign_str = match num.sign() {
819 Sign::Minus => "-",
820 _ => match format_sign {
821 FormatSign::Plus => "+",
822 FormatSign::Minus => "",
823 FormatSign::MinusOrSpace => " ",
824 },
825 };
826 let sign_prefix = format!("{sign_str}{prefix}");
827 let magnitude_str = self.add_magnitude_separators(raw_magnitude_str, &sign_prefix);
828 self.format_sign_and_align(
829 &AsciiStr::new(&magnitude_str),
830 &sign_prefix,
831 FormatAlign::Right,
832 )
833 }
834
835 pub fn format_string<T>(&self, s: &T) -> Result<String, FormatSpecError>
836 where
837 T: CharLen + Deref<Target = str>,
838 {
839 self.validate_format(FormatType::String)?;
840 match self.format_type {
841 Some(FormatType::String) | None => self
842 .format_sign_and_align(s, "", FormatAlign::Left)
843 .map(|mut value| {
844 if let Some(precision) = self.precision {
845 value.truncate(precision);
846 }
847 value
848 }),
849 _ => {
850 let ch = char::from(self.format_type.as_ref().unwrap());
851 Err(FormatSpecError::UnknownFormatCode(ch, "str"))
852 }
853 }
854 }
855
856 pub fn format_complex(&self, num: &Complex64) -> Result<String, FormatSpecError> {
857 let (formatted_re, formatted_im) = self.format_complex_re_im(num)?;
858 let magnitude_str = if self.format_type.is_none() && !formatted_re.is_empty() {
860 format!("({formatted_re}{formatted_im})")
861 } else {
862 format!("{formatted_re}{formatted_im}")
863 };
864 if let Some(FormatAlign::AfterSign) = &self.align {
865 return Err(FormatSpecError::AlignmentFlag);
866 }
867 match &self.fill.unwrap_or(' '.into()).to_char() {
868 Some('0') => Err(FormatSpecError::ZeroPadding),
869 _ => self.format_sign_and_align(&AsciiStr::new(&magnitude_str), "", FormatAlign::Right),
870 }
871 }
872
873 fn format_complex_re_im(&self, num: &Complex64) -> Result<(String, String), FormatSpecError> {
874 let mut formatted_re = String::new();
876 if num.re != 0.0 || num.re.is_negative_zero() || self.format_type.is_some() {
877 let sign_re = if num.re.is_sign_negative() && !num.is_nan() {
878 "-"
879 } else {
880 match self.sign.unwrap_or(FormatSign::Minus) {
881 FormatSign::Plus => "+",
882 FormatSign::Minus => "",
883 FormatSign::MinusOrSpace => " ",
884 }
885 };
886 let re = self.format_complex_float(num.re)?;
887 formatted_re = format!("{sign_re}{re}");
888 }
889 let sign_im = if num.im.is_sign_negative() && !num.im.is_nan() {
891 "-"
892 } else if formatted_re.is_empty() {
893 ""
894 } else {
895 "+"
896 };
897 let im = self.format_complex_float(num.im)?;
898 Ok((formatted_re, format!("{sign_im}{im}j")))
899 }
900
901 fn format_complex_float(&self, num: f64) -> Result<String, FormatSpecError> {
902 self.validate_format(FormatType::FixedPoint(Case::Lower))?;
903 let precision = self.precision.unwrap_or(6);
904 let magnitude = num.abs();
905 let magnitude_str = match &self.format_type {
906 Some(FormatType::Decimal)
907 | Some(FormatType::Binary)
908 | Some(FormatType::Octal)
909 | Some(FormatType::Hex(_))
910 | Some(FormatType::String)
911 | Some(FormatType::Character)
912 | Some(FormatType::Number(Case::Upper))
913 | Some(FormatType::Percentage)
914 | Some(FormatType::Unknown(_)) => {
915 let ch = char::from(self.format_type.as_ref().unwrap());
916 Err(FormatSpecError::UnknownFormatCode(ch, "complex"))
917 }
918 Some(FormatType::FixedPoint(case)) => Ok(float::format_fixed(
919 precision,
920 magnitude,
921 *case,
922 self.alternate_form,
923 )),
924 Some(FormatType::GeneralFormat(case)) | Some(FormatType::Number(case)) => {
925 let precision = if precision == 0 { 1 } else { precision };
926 Ok(float::format_general(
927 precision,
928 magnitude,
929 *case,
930 self.alternate_form,
931 false,
932 ))
933 }
934 Some(FormatType::Exponent(case)) => Ok(float::format_exponent(
935 precision,
936 magnitude,
937 *case,
938 self.alternate_form,
939 )),
940 None => match magnitude {
941 magnitude if magnitude.is_nan() => Ok("nan".to_owned()),
942 magnitude if magnitude.is_infinite() => Ok("inf".to_owned()),
943 _ => match self.precision {
944 Some(precision) => Ok(float::format_general(
945 precision,
946 magnitude,
947 Case::Lower,
948 self.alternate_form,
949 true,
950 )),
951 None => {
952 if magnitude.fract() == 0.0 {
953 Ok(magnitude.trunc().to_string())
954 } else {
955 Ok(magnitude.to_string())
956 }
957 }
958 },
959 },
960 }?;
961 match &self.grouping_option {
962 Some(fg) => {
963 let sep = char::from(fg);
964 let inter = self.get_separator_interval().try_into().unwrap();
965 let len = magnitude_str.len() as i32;
966 let separated_magnitude =
967 Self::add_magnitude_separators_for_char(magnitude_str, inter, sep, len);
968 Ok(separated_magnitude)
969 }
970 None => Ok(magnitude_str),
971 }
972 }
973
974 fn format_sign_and_align<T>(
975 &self,
976 magnitude_str: &T,
977 sign_str: &str,
978 default_align: FormatAlign,
979 ) -> Result<String, FormatSpecError>
980 where
981 T: CharLen + Deref<Target = str>,
982 {
983 let align = self.align.unwrap_or(default_align);
984
985 let num_chars = magnitude_str.char_len();
986 let fill_char = self.fill.unwrap_or(' '.into());
987 let fill_chars_needed: i32 = self.width.map_or(0, |w| {
988 cmp::max(0, (w as i32) - (num_chars as i32) - (sign_str.len() as i32))
989 });
990
991 let magnitude_str = magnitude_str.deref();
992 Ok(match align {
993 FormatAlign::Left => format!(
994 "{}{}{}",
995 sign_str,
996 magnitude_str,
997 Self::compute_fill_string(fill_char, fill_chars_needed)
998 ),
999 FormatAlign::Right => format!(
1000 "{}{}{}",
1001 Self::compute_fill_string(fill_char, fill_chars_needed),
1002 sign_str,
1003 magnitude_str
1004 ),
1005 FormatAlign::AfterSign => format!(
1006 "{}{}{}",
1007 sign_str,
1008 Self::compute_fill_string(fill_char, fill_chars_needed),
1009 magnitude_str
1010 ),
1011 FormatAlign::Center => {
1012 let left_fill_chars_needed = fill_chars_needed / 2;
1013 let right_fill_chars_needed = fill_chars_needed - left_fill_chars_needed;
1014 let left_fill_string = Self::compute_fill_string(fill_char, left_fill_chars_needed);
1015 let right_fill_string =
1016 Self::compute_fill_string(fill_char, right_fill_chars_needed);
1017 format!("{left_fill_string}{sign_str}{magnitude_str}{right_fill_string}")
1018 }
1019 })
1020 }
1021}
1022
1023pub trait CharLen {
1024 fn char_len(&self) -> usize;
1026}
1027
1028struct AsciiStr<'a> {
1029 inner: &'a str,
1030}
1031
1032impl<'a> AsciiStr<'a> {
1033 const fn new(inner: &'a str) -> Self {
1034 Self { inner }
1035 }
1036}
1037
1038impl CharLen for AsciiStr<'_> {
1039 fn char_len(&self) -> usize {
1040 self.inner.len()
1041 }
1042}
1043
1044impl Deref for AsciiStr<'_> {
1045 type Target = str;
1046
1047 fn deref(&self) -> &Self::Target {
1048 self.inner
1049 }
1050}
1051
1052#[derive(Clone, Copy, Debug, PartialEq)]
1053pub enum FormatSpecError {
1054 DecimalDigitsTooMany,
1055 PrecisionTooBig,
1056 InvalidFormatSpecifier,
1057 UnspecifiedFormat(char, char),
1058 ExclusiveFormat(char, char),
1059 UnknownFormatCode(char, &'static str),
1060 PrecisionNotAllowed,
1061 NotAllowed(&'static str),
1062 UnableToConvert,
1063 CodeNotInRange,
1064 ZeroPadding,
1065 AlignmentFlag,
1066 NotImplemented(char, &'static str),
1067}
1068
1069#[derive(Clone, Copy, Debug, PartialEq)]
1070pub enum FormatParseError {
1071 UnmatchedBracket,
1072 MissingStartBracket,
1073 UnescapedStartBracketInLiteral,
1074 InvalidFormatSpecifier,
1075 UnknownConversion,
1076 EmptyAttribute,
1077 MissingRightBracket,
1078 InvalidCharacterAfterRightBracket,
1079}
1080
1081impl FromStr for FormatSpec {
1082 type Err = FormatSpecError;
1083 fn from_str(s: &str) -> Result<Self, Self::Err> {
1084 Self::parse(s)
1085 }
1086}
1087
1088#[derive(Debug, PartialEq)]
1089pub enum FieldNamePart {
1090 Attribute(Wtf8Buf),
1091 Index(usize),
1092 StringIndex(Wtf8Buf),
1093}
1094
1095impl FieldNamePart {
1096 fn parse_part(
1097 chars: &mut impl PeekingNext<Item = CodePoint>,
1098 ) -> Result<Option<Self>, FormatParseError> {
1099 chars
1100 .next()
1101 .map(|ch| match ch.to_char_lossy() {
1102 '.' => {
1103 let mut attribute = Wtf8Buf::new();
1104 for ch in chars.peeking_take_while(|ch| *ch != '.' && *ch != '[') {
1105 attribute.push(ch);
1106 }
1107 if attribute.is_empty() {
1108 Err(FormatParseError::EmptyAttribute)
1109 } else {
1110 Ok(Self::Attribute(attribute))
1111 }
1112 }
1113 '[' => {
1114 let mut index = Wtf8Buf::new();
1115 for ch in chars {
1116 if ch == ']' {
1117 return if index.is_empty() {
1118 Err(FormatParseError::EmptyAttribute)
1119 } else if let Some(index) = parse_usize(&index) {
1120 Ok(Self::Index(index))
1121 } else {
1122 Ok(Self::StringIndex(index))
1123 };
1124 }
1125 index.push(ch);
1126 }
1127 Err(FormatParseError::MissingRightBracket)
1128 }
1129 _ => Err(FormatParseError::InvalidCharacterAfterRightBracket),
1130 })
1131 .transpose()
1132 }
1133}
1134
1135#[derive(Debug, PartialEq)]
1136pub enum FieldType {
1137 Auto,
1138 Index(usize),
1139 Keyword(Wtf8Buf),
1140}
1141
1142#[derive(Debug, PartialEq)]
1143pub struct FieldName {
1144 pub field_type: FieldType,
1145 pub parts: Vec<FieldNamePart>,
1146}
1147
1148fn parse_usize(s: &Wtf8) -> Option<usize> {
1149 s.as_str().ok().and_then(|s| s.parse().ok())
1150}
1151
1152impl FieldName {
1153 pub fn parse(text: &Wtf8) -> Result<Self, FormatParseError> {
1154 let mut chars = text.code_points().peekable();
1155 let first: Wtf8Buf = chars
1156 .peeking_take_while(|ch| *ch != '.' && *ch != '[')
1157 .collect();
1158
1159 let field_type = if first.is_empty() {
1160 FieldType::Auto
1161 } else if let Some(index) = parse_usize(&first) {
1162 FieldType::Index(index)
1163 } else {
1164 FieldType::Keyword(first)
1165 };
1166
1167 let mut parts = Vec::new();
1168 while let Some(part) = FieldNamePart::parse_part(&mut chars)? {
1169 parts.push(part)
1170 }
1171
1172 Ok(Self { field_type, parts })
1173 }
1174}
1175
1176#[derive(Debug, PartialEq)]
1177pub enum FormatPart {
1178 Field {
1179 field_name: Wtf8Buf,
1180 conversion_spec: Option<CodePoint>,
1181 format_spec: Wtf8Buf,
1182 },
1183 Literal(Wtf8Buf),
1184}
1185
1186#[derive(Debug, PartialEq)]
1187pub struct FormatString {
1188 pub format_parts: Vec<FormatPart>,
1189}
1190
1191impl FormatString {
1192 fn parse_literal_single(text: &Wtf8) -> Result<(CodePoint, &Wtf8), FormatParseError> {
1193 let mut chars = text.code_points();
1194 let first_char = chars.next().unwrap();
1196 if first_char == '{' || first_char == '}' {
1198 let maybe_next_char = chars.next();
1199 return if maybe_next_char.is_none() || maybe_next_char.unwrap() != first_char {
1201 Err(FormatParseError::UnescapedStartBracketInLiteral)
1202 } else {
1203 Ok((first_char, chars.as_wtf8()))
1204 };
1205 }
1206 Ok((first_char, chars.as_wtf8()))
1207 }
1208
1209 fn parse_literal(text: &Wtf8) -> Result<(FormatPart, &Wtf8), FormatParseError> {
1210 let mut cur_text = text;
1211 let mut result_string = Wtf8Buf::new();
1212 while !cur_text.is_empty() {
1213 match Self::parse_literal_single(cur_text) {
1214 Ok((next_char, remaining)) => {
1215 result_string.push(next_char);
1216 cur_text = remaining;
1217 }
1218 Err(err) => {
1219 return if !result_string.is_empty() {
1220 Ok((FormatPart::Literal(result_string), cur_text))
1221 } else {
1222 Err(err)
1223 };
1224 }
1225 }
1226 }
1227 Ok((FormatPart::Literal(result_string), "".as_ref()))
1228 }
1229
1230 fn parse_part_in_brackets(text: &Wtf8) -> Result<FormatPart, FormatParseError> {
1231 let mut chars = text.code_points().peekable();
1232
1233 let mut left = Wtf8Buf::new();
1234 let mut right = Wtf8Buf::new();
1235
1236 let mut split = false;
1237 let mut selected = &mut left;
1238 let mut inside_brackets = false;
1239
1240 while let Some(char) = chars.next() {
1241 if char == '[' {
1242 inside_brackets = true;
1243
1244 selected.push(char);
1245
1246 while let Some(next_char) = chars.next() {
1247 selected.push(next_char);
1248
1249 if next_char == ']' {
1250 inside_brackets = false;
1251 break;
1252 }
1253 if chars.peek().is_none() {
1254 return Err(FormatParseError::MissingRightBracket);
1255 }
1256 }
1257 } else if char == ':' && !split && !inside_brackets {
1258 split = true;
1259 selected = &mut right;
1260 } else {
1261 selected.push(char);
1262 }
1263 }
1264
1265 let arg_part: &Wtf8 = &left;
1267
1268 let format_spec = if split { right } else { Wtf8Buf::new() };
1269
1270 let parts: Vec<&Wtf8> = arg_part.splitn(2, "!".as_ref()).collect();
1272 let arg_part = parts[0];
1274
1275 let conversion_spec = parts
1276 .get(1)
1277 .map(|conversion| {
1278 conversion
1280 .code_points()
1281 .exactly_one()
1282 .map_err(|_| FormatParseError::UnknownConversion)
1283 })
1284 .transpose()?;
1285
1286 Ok(FormatPart::Field {
1287 field_name: arg_part.to_owned(),
1288 conversion_spec,
1289 format_spec,
1290 })
1291 }
1292
1293 fn parse_spec(text: &Wtf8) -> Result<(FormatPart, &Wtf8), FormatParseError> {
1294 let mut nested = false;
1295 let mut end_bracket_pos = None;
1296 let mut left = Wtf8Buf::new();
1297
1298 for (idx, c) in text.code_point_indices() {
1300 if idx == 0 {
1301 if c != '{' {
1302 return Err(FormatParseError::MissingStartBracket);
1303 }
1304 } else if c == '{' {
1305 if nested {
1306 return Err(FormatParseError::InvalidFormatSpecifier);
1307 } else {
1308 nested = true;
1309 left.push(c);
1310 continue;
1311 }
1312 } else if c == '}' {
1313 if nested {
1314 nested = false;
1315 left.push(c);
1316 continue;
1317 } else {
1318 end_bracket_pos = Some(idx);
1319 break;
1320 }
1321 } else {
1322 left.push(c);
1323 }
1324 }
1325 if let Some(pos) = end_bracket_pos {
1326 let right = &text[pos..];
1327 let format_part = Self::parse_part_in_brackets(&left)?;
1328 Ok((format_part, &right[1..]))
1329 } else {
1330 Err(FormatParseError::UnmatchedBracket)
1331 }
1332 }
1333}
1334
1335pub trait FromTemplate<'a>: Sized {
1336 type Err;
1337 fn from_str(s: &'a Wtf8) -> Result<Self, Self::Err>;
1338}
1339
1340impl<'a> FromTemplate<'a> for FormatString {
1341 type Err = FormatParseError;
1342
1343 fn from_str(text: &'a Wtf8) -> Result<Self, Self::Err> {
1344 let mut cur_text: &Wtf8 = text;
1345 let mut parts: Vec<FormatPart> = Vec::new();
1346 while !cur_text.is_empty() {
1347 cur_text = Self::parse_literal(cur_text)
1350 .or_else(|_| Self::parse_spec(cur_text))
1351 .map(|(part, new_text)| {
1352 parts.push(part);
1353 new_text
1354 })?;
1355 }
1356 Ok(Self {
1357 format_parts: parts,
1358 })
1359 }
1360}
1361
1362#[cfg(test)]
1363mod tests {
1364 use super::*;
1365
1366 #[test]
1367 fn test_fill_and_align() {
1368 let parse_fill_and_align = |text| {
1369 let (fill, align, rest) = parse_fill_and_align(str::as_ref(text));
1370 (
1371 fill.and_then(CodePoint::to_char),
1372 align,
1373 rest.as_str().unwrap(),
1374 )
1375 };
1376 assert_eq!(
1377 parse_fill_and_align(" <"),
1378 (Some(' '), Some(FormatAlign::Left), "")
1379 );
1380 assert_eq!(
1381 parse_fill_and_align(" <22"),
1382 (Some(' '), Some(FormatAlign::Left), "22")
1383 );
1384 assert_eq!(
1385 parse_fill_and_align("<22"),
1386 (None, Some(FormatAlign::Left), "22")
1387 );
1388 assert_eq!(
1389 parse_fill_and_align(" ^^"),
1390 (Some(' '), Some(FormatAlign::Center), "^")
1391 );
1392 assert_eq!(
1393 parse_fill_and_align("==="),
1394 (Some('='), Some(FormatAlign::AfterSign), "=")
1395 );
1396 }
1397
1398 #[test]
1399 fn test_width_only() {
1400 let expected = Ok(FormatSpec {
1401 conversion: None,
1402 fill: None,
1403 align: None,
1404 sign: None,
1405 alternate_form: false,
1406 width: Some(33),
1407 grouping_option: None,
1408 precision: None,
1409 format_type: None,
1410 });
1411 assert_eq!(FormatSpec::parse("33"), expected);
1412 }
1413
1414 #[test]
1415 fn test_fill_and_width() {
1416 let expected = Ok(FormatSpec {
1417 conversion: None,
1418 fill: Some('<'.into()),
1419 align: Some(FormatAlign::Right),
1420 sign: None,
1421 alternate_form: false,
1422 width: Some(33),
1423 grouping_option: None,
1424 precision: None,
1425 format_type: None,
1426 });
1427 assert_eq!(FormatSpec::parse("<>33"), expected);
1428 }
1429
1430 #[test]
1431 fn test_all() {
1432 let expected = Ok(FormatSpec {
1433 conversion: None,
1434 fill: Some('<'.into()),
1435 align: Some(FormatAlign::Right),
1436 sign: Some(FormatSign::Minus),
1437 alternate_form: true,
1438 width: Some(23),
1439 grouping_option: Some(FormatGrouping::Comma),
1440 precision: Some(11),
1441 format_type: Some(FormatType::Binary),
1442 });
1443 assert_eq!(FormatSpec::parse("<>-#23,.11b"), expected);
1444 }
1445
1446 fn format_bool(text: &str, value: bool) -> Result<String, FormatSpecError> {
1447 FormatSpec::parse(text).and_then(|spec| spec.format_bool(value))
1448 }
1449
1450 #[test]
1451 fn test_format_bool() {
1452 assert_eq!(format_bool("b", true), Ok("1".to_owned()));
1453 assert_eq!(format_bool("b", false), Ok("0".to_owned()));
1454 assert_eq!(format_bool("d", true), Ok("1".to_owned()));
1455 assert_eq!(format_bool("d", false), Ok("0".to_owned()));
1456 assert_eq!(format_bool("o", true), Ok("1".to_owned()));
1457 assert_eq!(format_bool("o", false), Ok("0".to_owned()));
1458 assert_eq!(format_bool("n", true), Ok("1".to_owned()));
1459 assert_eq!(format_bool("n", false), Ok("0".to_owned()));
1460 assert_eq!(format_bool("x", true), Ok("1".to_owned()));
1461 assert_eq!(format_bool("x", false), Ok("0".to_owned()));
1462 assert_eq!(format_bool("X", true), Ok("1".to_owned()));
1463 assert_eq!(format_bool("X", false), Ok("0".to_owned()));
1464 assert_eq!(format_bool("g", true), Ok("1".to_owned()));
1465 assert_eq!(format_bool("g", false), Ok("0".to_owned()));
1466 assert_eq!(format_bool("G", true), Ok("1".to_owned()));
1467 assert_eq!(format_bool("G", false), Ok("0".to_owned()));
1468 assert_eq!(format_bool("c", true), Ok("\x01".to_owned()));
1469 assert_eq!(format_bool("c", false), Ok("\x00".to_owned()));
1470 assert_eq!(format_bool("e", true), Ok("1.000000e+00".to_owned()));
1471 assert_eq!(format_bool("e", false), Ok("0.000000e+00".to_owned()));
1472 assert_eq!(format_bool("E", true), Ok("1.000000E+00".to_owned()));
1473 assert_eq!(format_bool("E", false), Ok("0.000000E+00".to_owned()));
1474 assert_eq!(format_bool("f", true), Ok("1.000000".to_owned()));
1475 assert_eq!(format_bool("f", false), Ok("0.000000".to_owned()));
1476 assert_eq!(format_bool("F", true), Ok("1.000000".to_owned()));
1477 assert_eq!(format_bool("F", false), Ok("0.000000".to_owned()));
1478 assert_eq!(format_bool("%", true), Ok("100.000000%".to_owned()));
1479 assert_eq!(format_bool("%", false), Ok("0.000000%".to_owned()));
1480 }
1481
1482 #[test]
1483 fn test_format_int() {
1484 assert_eq!(
1485 FormatSpec::parse("d")
1486 .unwrap()
1487 .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
1488 Ok("16".to_owned())
1489 );
1490 assert_eq!(
1491 FormatSpec::parse("x")
1492 .unwrap()
1493 .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
1494 Ok("10".to_owned())
1495 );
1496 assert_eq!(
1497 FormatSpec::parse("b")
1498 .unwrap()
1499 .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
1500 Ok("10000".to_owned())
1501 );
1502 assert_eq!(
1503 FormatSpec::parse("o")
1504 .unwrap()
1505 .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
1506 Ok("20".to_owned())
1507 );
1508 assert_eq!(
1509 FormatSpec::parse("+d")
1510 .unwrap()
1511 .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
1512 Ok("+16".to_owned())
1513 );
1514 assert_eq!(
1515 FormatSpec::parse("^ 5d")
1516 .unwrap()
1517 .format_int(&BigInt::from_bytes_be(Sign::Minus, b"\x10")),
1518 Ok(" -16 ".to_owned())
1519 );
1520 assert_eq!(
1521 FormatSpec::parse("0>+#10x")
1522 .unwrap()
1523 .format_int(&BigInt::from_bytes_be(Sign::Plus, b"\x10")),
1524 Ok("00000+0x10".to_owned())
1525 );
1526 }
1527
1528 #[test]
1529 fn test_format_int_sep() {
1530 let spec = FormatSpec::parse(",").expect("");
1531 assert_eq!(spec.grouping_option, Some(FormatGrouping::Comma));
1532 assert_eq!(
1533 spec.format_int(&BigInt::from_str("1234567890123456789012345678").unwrap()),
1534 Ok("1,234,567,890,123,456,789,012,345,678".to_owned())
1535 );
1536 }
1537
1538 #[test]
1539 fn test_format_int_width_and_grouping() {
1540 let spec = FormatSpec::parse("10,").unwrap();
1542 let result = spec.format_int(&BigInt::from(1234)).unwrap();
1543 assert_eq!(result, " 1,234"); }
1545
1546 #[test]
1547 fn test_format_int_padding_with_grouping() {
1548 let spec1 = FormatSpec::parse("010,").unwrap();
1550 let result1 = spec1.format_int(&BigInt::from(1234)).unwrap();
1551 assert_eq!(result1, "00,001,234");
1552
1553 let spec2 = FormatSpec::parse("010,").unwrap();
1555 let result2 = spec2.format_int(&BigInt::from(-1234)).unwrap();
1556 assert_eq!(result2, "-0,001,234");
1557
1558 let spec3 = FormatSpec::parse("=10,").unwrap();
1560 let result3 = spec3.format_int(&BigInt::from(-1234)).unwrap();
1561 assert_eq!(result3, "- 1,234");
1562
1563 let spec4 = FormatSpec::parse("=10,").unwrap();
1565 let result4 = spec4.format_int(&BigInt::from(1234)).unwrap();
1566 assert_eq!(result4, " 1,234");
1567 }
1568
1569 #[test]
1570 fn test_format_int_non_aftersign_zero_padding() {
1571 let spec = FormatSpec::parse("0>10,").unwrap();
1573 let result = spec.format_int(&BigInt::from(1234)).unwrap();
1574 assert_eq!(result, "000001,234");
1575 }
1576
1577 #[test]
1578 fn test_format_parse() {
1579 let expected = Ok(FormatString {
1580 format_parts: vec![
1581 FormatPart::Literal("abcd".into()),
1582 FormatPart::Field {
1583 field_name: "1".into(),
1584 conversion_spec: None,
1585 format_spec: "".into(),
1586 },
1587 FormatPart::Literal(":".into()),
1588 FormatPart::Field {
1589 field_name: "key".into(),
1590 conversion_spec: None,
1591 format_spec: "".into(),
1592 },
1593 ],
1594 });
1595
1596 assert_eq!(FormatString::from_str("abcd{1}:{key}".as_ref()), expected);
1597 }
1598
1599 #[test]
1600 fn test_format_parse_multi_byte_char() {
1601 assert!(FormatString::from_str("{a:%ЫйЯЧ}".as_ref()).is_ok());
1602 }
1603
1604 #[test]
1605 fn test_format_parse_fail() {
1606 assert_eq!(
1607 FormatString::from_str("{s".as_ref()),
1608 Err(FormatParseError::UnmatchedBracket)
1609 );
1610 }
1611
1612 #[test]
1613 fn test_square_brackets_inside_format() {
1614 assert_eq!(
1615 FormatString::from_str("{[:123]}".as_ref()),
1616 Ok(FormatString {
1617 format_parts: vec![FormatPart::Field {
1618 field_name: "[:123]".into(),
1619 conversion_spec: None,
1620 format_spec: "".into(),
1621 }],
1622 }),
1623 );
1624
1625 assert_eq!(FormatString::from_str("{asdf[:123]asdf}".as_ref()), {
1626 Ok(FormatString {
1627 format_parts: vec![FormatPart::Field {
1628 field_name: "asdf[:123]asdf".into(),
1629 conversion_spec: None,
1630 format_spec: "".into(),
1631 }],
1632 })
1633 });
1634
1635 assert_eq!(FormatString::from_str("{[1234}".as_ref()), {
1636 Err(FormatParseError::MissingRightBracket)
1637 });
1638 }
1639
1640 #[test]
1641 fn test_format_parse_escape() {
1642 let expected = Ok(FormatString {
1643 format_parts: vec![
1644 FormatPart::Literal("{".into()),
1645 FormatPart::Field {
1646 field_name: "key".into(),
1647 conversion_spec: None,
1648 format_spec: "".into(),
1649 },
1650 FormatPart::Literal("}ddfe".into()),
1651 ],
1652 });
1653
1654 assert_eq!(FormatString::from_str("{{{key}}}ddfe".as_ref()), expected);
1655 }
1656
1657 #[test]
1658 fn test_format_invalid_specification() {
1659 assert_eq!(
1660 FormatSpec::parse("%3"),
1661 Err(FormatSpecError::InvalidFormatSpecifier)
1662 );
1663 assert_eq!(
1664 FormatSpec::parse(".2fa"),
1665 Err(FormatSpecError::InvalidFormatSpecifier)
1666 );
1667 assert_eq!(
1668 FormatSpec::parse("ds"),
1669 Err(FormatSpecError::InvalidFormatSpecifier)
1670 );
1671 assert_eq!(
1672 FormatSpec::parse("x+"),
1673 Err(FormatSpecError::InvalidFormatSpecifier)
1674 );
1675 assert_eq!(
1676 FormatSpec::parse("b4"),
1677 Err(FormatSpecError::InvalidFormatSpecifier)
1678 );
1679 assert_eq!(
1680 FormatSpec::parse("o!"),
1681 Err(FormatSpecError::InvalidFormatSpecifier)
1682 );
1683 assert_eq!(
1684 FormatSpec::parse("d "),
1685 Err(FormatSpecError::InvalidFormatSpecifier)
1686 );
1687 }
1688
1689 #[test]
1690 fn test_parse_field_name() {
1691 let parse = |s: &str| FieldName::parse(s.as_ref());
1692 assert_eq!(
1693 parse(""),
1694 Ok(FieldName {
1695 field_type: FieldType::Auto,
1696 parts: Vec::new(),
1697 })
1698 );
1699 assert_eq!(
1700 parse("0"),
1701 Ok(FieldName {
1702 field_type: FieldType::Index(0),
1703 parts: Vec::new(),
1704 })
1705 );
1706 assert_eq!(
1707 parse("key"),
1708 Ok(FieldName {
1709 field_type: FieldType::Keyword("key".into()),
1710 parts: Vec::new(),
1711 })
1712 );
1713 assert_eq!(
1714 parse("key.attr[0][string]"),
1715 Ok(FieldName {
1716 field_type: FieldType::Keyword("key".into()),
1717 parts: vec![
1718 FieldNamePart::Attribute("attr".into()),
1719 FieldNamePart::Index(0),
1720 FieldNamePart::StringIndex("string".into())
1721 ],
1722 })
1723 );
1724 assert_eq!(parse("key.."), Err(FormatParseError::EmptyAttribute));
1725 assert_eq!(parse("key[]"), Err(FormatParseError::EmptyAttribute));
1726 assert_eq!(parse("key["), Err(FormatParseError::MissingRightBracket));
1727 assert_eq!(
1728 parse("key[0]after"),
1729 Err(FormatParseError::InvalidCharacterAfterRightBracket)
1730 );
1731 }
1732}