12#![allow(clippy::too_many_arguments)]34use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val, ValType};56#[derive(Debug, Clone)]7pub enum FormatError {8 TruncatedFormatCode,9 UnrecognizedConversionType(char),1011 NotEnoughValues,1213 CannotUseStarWidthWithObject,14 MappingKeysRequired,15 NoSuchFormatField(Rc<str>),16}1718impl From<FormatError> for LocError {19 fn from(e: FormatError) -> Self {20 Self::new(Format(e))21 }22}2324use std::rc::Rc;25use FormatError::*;2627type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;2829pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> {30 if str.is_empty() {31 return Err(TruncatedFormatCode);32 }33 let bytes = str.as_bytes();34 if bytes[0] == b'(' {35 let mut i = 1;36 while i < bytes.len() {37 if bytes[i] == b')' {38 return Ok((&str[1..i as usize], &str[i as usize + 1..]));39 }40 i += 1;41 }42 Err(TruncatedFormatCode)43 } else {44 Ok(("", str))45 }46}4748#[cfg(test)]49pub mod tests_key {50 use super::*;5152 #[test]53 fn parse_key() {54 assert_eq!(55 try_parse_mapping_key("(hello ) world").unwrap(),56 ("hello ", " world")57 );58 assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));59 assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));60 assert_eq!(61 try_parse_mapping_key(" () world").unwrap(),62 ("", " () world")63 );64 }6566 #[test]67 #[should_panic]68 fn parse_key_missing_start() {69 try_parse_mapping_key("").unwrap();70 }7172 #[test]73 #[should_panic]74 fn parse_key_missing_end() {75 try_parse_mapping_key("( ").unwrap();76 }77}7879#[derive(Default, Debug)]80pub struct CFlags {81 pub alt: bool,82 pub zero: bool,83 pub left: bool,84 pub blank: bool,85 pub sign: bool,86}8788pub fn try_parse_cflags(str: &str) -> ParseResult<CFlags> {89 if str.is_empty() {90 return Err(TruncatedFormatCode);91 }92 let bytes = str.as_bytes();93 let mut i = 0;94 let mut out = CFlags::default();95 loop {96 if bytes.len() == i {97 return Err(TruncatedFormatCode);98 }99 match bytes[i] {100 b'#' => out.alt = true,101 b'0' => out.zero = true,102 b'-' => out.left = true,103 b' ' => out.blank = true,104 b'+' => out.sign = true,105 _ => break,106 }107 i += 1;108 }109 Ok((out, &str[i..]))110}111112#[derive(Debug, PartialEq)]113pub enum Width {114 Star,115 Fixed(usize),116}117pub fn try_parse_field_width(str: &str) -> ParseResult<Width> {118 if str.is_empty() {119 return Err(TruncatedFormatCode);120 }121 let bytes = str.as_bytes();122 if bytes[0] == b'*' {123 return Ok((Width::Star, &str[1..]));124 }125 let mut out: usize = 0;126 let mut digits = 0;127 while let Some(digit) = (bytes[digits] as char).to_digit(10) {128 out *= 10;129 out += digit as usize;130 digits += 1;131 if digits == bytes.len() {132 return Err(TruncatedFormatCode);133 }134 }135 Ok((Width::Fixed(out), &str[digits..]))136}137138pub fn try_parse_precision(str: &str) -> ParseResult<Option<Width>> {139 if str.is_empty() {140 return Err(TruncatedFormatCode);141 }142 let bytes = str.as_bytes();143 if bytes[0] == b'.' {144 try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))145 } else {146 Ok((None, str))147 }148}149150151pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> {152 if str.is_empty() {153 return Err(TruncatedFormatCode);154 }155 let bytes = str.as_bytes();156 let mut idx = 0;157 while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {158 idx += 1;159 if bytes.len() == idx {160 return Err(TruncatedFormatCode);161 }162 }163 Ok(((), &str[idx..]))164}165166#[derive(Debug, PartialEq)]167pub enum ConvTypeV {168 Decimal,169 Octal,170 Hexadecimal,171 Scientific,172 Float,173 Shorter,174 Char,175 String,176 Percent,177}178pub struct ConvType {179 v: ConvTypeV,180 caps: bool,181}182183pub fn parse_conversion_type(str: &str) -> ParseResult<ConvType> {184 if str.is_empty() {185 return Err(TruncatedFormatCode);186 }187188 let code = str.as_bytes()[0];189 let v: (ConvTypeV, bool) = match code {190 b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),191 b'o' => (ConvTypeV::Octal, false),192 b'x' => (ConvTypeV::Hexadecimal, false),193 b'X' => (ConvTypeV::Hexadecimal, true),194 b'e' => (ConvTypeV::Scientific, false),195 b'E' => (ConvTypeV::Scientific, true),196 b'f' => (ConvTypeV::Float, false),197 b'F' => (ConvTypeV::Float, true),198 b'g' => (ConvTypeV::Shorter, false),199 b'G' => (ConvTypeV::Shorter, true),200 b'c' => (ConvTypeV::Char, false),201 b's' => (ConvTypeV::String, false),202 b'%' => (ConvTypeV::Percent, false),203 c => return Err(UnrecognizedConversionType(c as char)),204 };205206 Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))207}208209#[derive(Debug)]210pub struct Code<'s> {211 mkey: &'s str,212 cflags: CFlags,213 width: Width,214 precision: Option<Width>,215 convtype: ConvTypeV,216 caps: bool,217}218pub fn parse_code(str: &str) -> ParseResult<Code> {219 if str.is_empty() {220 return Err(TruncatedFormatCode);221 }222 let (mkey, str) = try_parse_mapping_key(str)?;223 let (cflags, str) = try_parse_cflags(str)?;224 let (width, str) = try_parse_field_width(str)?;225 let (precision, str) = try_parse_precision(str)?;226 let (_, str) = try_parse_length_modifier(str)?;227 let (convtype, str) = parse_conversion_type(str)?;228229 Ok((230 Code {231 mkey,232 cflags,233 width,234 precision,235 convtype: convtype.v,236 caps: convtype.caps,237 },238 str,239 ))240}241242#[derive(Debug)]243pub enum Element<'s> {244 String(&'s str),245 Code(Code<'s>),246}247pub fn parse_codes(mut str: &str) -> Result<Vec<Element>> {248 let mut bytes = str.as_bytes();249 let mut out = vec![];250 let mut offset = 0;251252 loop {253 while offset != bytes.len() && bytes[offset] != b'%' {254 offset += 1;255 }256 if offset != 0 {257 out.push(Element::String(&str[0..offset]));258 }259 if offset == bytes.len() {260 return Ok(out);261 }262 str = &str[offset + 1..];263 let (code, nstr) = parse_code(str)?;264 str = nstr;265 bytes = str.as_bytes();266 offset = 0;267268 out.push(Element::Code(code))269 }270}271272const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";273274#[inline]275pub fn render_integer(276 out: &mut String,277 iv: i64,278 padding: usize,279 precision: usize,280 blank: bool,281 sign: bool,282 radix: i64,283 prefix: &str,284 caps: bool,285) {286 287 288 let digits = if iv == 0 {289 vec![0u8]290 } else {291 let mut v = iv.abs();292 let mut nums = Vec::with_capacity(1);293 while v > 0 {294 nums.push((v % radix) as u8);295 v /= radix;296 }297 nums298 };299 let neg = iv < 0;300 let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });301 let zp2 = zp302 .max(precision)303 .saturating_sub(prefix.len() + digits.len());304305 if neg {306 out.push('-')307 } else if sign {308 out.push('+');309 } else if blank {310 out.push(' ');311 }312313 out.reserve(zp2);314 for _ in 0..zp2 {315 out.push('0');316 }317 out.push_str(prefix);318319 for digit in digits.into_iter().rev() {320 let ch = NUMBERS[digit as usize] as char;321 out.push(if caps { ch.to_ascii_uppercase() } else { ch });322 }323}324325pub fn render_decimal(326 out: &mut String,327 iv: i64,328 padding: usize,329 precision: usize,330 blank: bool,331 sign: bool,332) {333 render_integer(out, iv, padding, precision, blank, sign, 10, "", false)334}335pub fn render_octal(336 out: &mut String,337 iv: i64,338 padding: usize,339 precision: usize,340 alt: bool,341 blank: bool,342 sign: bool,343) {344 render_integer(345 out,346 iv,347 padding,348 precision,349 blank,350 sign,351 8,352 if alt && iv != 0 { "0" } else { "" },353 false,354 )355}356pub fn render_hexadecimal(357 out: &mut String,358 iv: i64,359 padding: usize,360 precision: usize,361 alt: bool,362 blank: bool,363 sign: bool,364 caps: bool,365) {366 render_integer(367 out,368 iv,369 padding,370 precision,371 blank,372 sign,373 16,374 match (alt, caps) {375 (true, true) => "0X",376 (true, false) => "0x",377 (false, _) => "",378 },379 caps,380 )381}382383pub fn render_float(384 out: &mut String,385 n: f64,386 mut padding: usize,387 precision: usize,388 blank: bool,389 sign: bool,390 ensure_pt: bool,391 trailing: bool,392) {393 let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };394 padding = padding.saturating_sub(dot_size + precision);395 render_decimal(out, n.floor() as i64, padding, 0, blank, sign);396 if precision == 0 {397 if ensure_pt {398 out.push('.');399 }400 return;401 }402 let frac = n403 .fract()404 .mul_add(10.0_f64.powf(precision as f64), 0.5)405 .floor();406 if trailing || frac > 0.0 {407 out.push('.');408 let mut frac_str = String::new();409 render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);410 let mut trim = frac_str.len();411 if !trailing {412 for b in frac_str.as_bytes().iter().rev() {413 if *b == b'0' {414 trim -= 1;415 }416 }417 }418 out.push_str(&frac_str[..trim]);419 } else if ensure_pt {420 out.push('.');421 }422}423424pub fn render_float_sci(425 out: &mut String,426 n: f64,427 mut padding: usize,428 precision: usize,429 blank: bool,430 sign: bool,431 ensure_pt: bool,432 trailing: bool,433 caps: bool,434) {435 let exponent = n.log10().floor();436 let mantissa = if exponent as i16 == -324 {437 n * 10.0 / 10.0_f64.powf(exponent + 1.0)438 } else {439 n / 10.0_f64.powf(exponent)440 };441 let mut exponent_str = String::new();442 render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);443444 445 padding = padding.saturating_sub(exponent_str.len() + 1);446447 render_float(448 out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,449 );450 out.push(if caps { 'E' } else { 'e' });451 out.push_str(&exponent_str);452}453454pub fn format_code(455 out: &mut String,456 value: &Val,457 code: &Code,458 width: usize,459 precision: Option<usize>,460) -> Result<()> {461 let clfags = &code.cflags;462 let (fpprec, iprec) = match precision {463 Some(v) => (v, v),464 None => (6, 0),465 };466 let padding = if clfags.zero && !clfags.left {467 width468 } else {469 0470 };471472 473 let mut tmp_out = String::new();474475 match code.convtype {476 ConvTypeV::String => tmp_out.push_str(&value.clone().to_string()?),477 ConvTypeV::Decimal => {478 let value = value.clone().try_cast_num("%d/%u/%i requires number")?;479 render_decimal(480 &mut tmp_out,481 value as i64,482 padding,483 iprec,484 clfags.blank,485 clfags.sign,486 );487 }488 ConvTypeV::Octal => {489 let value = value.clone().try_cast_num("%o requires number")?;490 render_octal(491 &mut tmp_out,492 value as i64,493 padding,494 iprec,495 clfags.alt,496 clfags.blank,497 clfags.sign,498 );499 }500 ConvTypeV::Hexadecimal => {501 let value = value.clone().try_cast_num("%x/%X requires number")?;502 render_hexadecimal(503 &mut tmp_out,504 value as i64,505 padding,506 iprec,507 clfags.alt,508 clfags.blank,509 clfags.sign,510 code.caps,511 );512 }513 ConvTypeV::Scientific => {514 let value = value.clone().try_cast_num("%e/%E requires number")?;515 render_float_sci(516 &mut tmp_out,517 value,518 padding,519 fpprec,520 clfags.blank,521 clfags.sign,522 clfags.alt,523 true,524 code.caps,525 );526 }527 ConvTypeV::Float => {528 let value = value.clone().try_cast_num("%e/%E requires number")?;529 render_float(530 &mut tmp_out,531 value,532 padding,533 fpprec,534 clfags.blank,535 clfags.sign,536 clfags.alt,537 true,538 );539 }540 ConvTypeV::Shorter => {541 let value = value.clone().try_cast_num("%g/%G requires number")?;542 let exponent = value.log10().floor();543 if exponent < -4.0 || exponent >= fpprec as f64 {544 render_float_sci(545 &mut tmp_out,546 value,547 padding,548 fpprec - 1,549 clfags.blank,550 clfags.sign,551 clfags.alt,552 clfags.alt,553 code.caps,554 );555 } else {556 let digits_before_pt = 1.max(exponent as usize + 1);557 render_float(558 &mut tmp_out,559 value,560 padding,561 fpprec - digits_before_pt,562 clfags.blank,563 clfags.sign,564 clfags.alt,565 clfags.alt,566 );567 }568 }569 ConvTypeV::Char => match value.clone().unwrap_if_lazy()? {570 Val::Num(n) => tmp_out.push(571 std::char::from_u32(n as u32)572 .ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,573 ),574 Val::Str(s) => {575 if s.chars().count() != 1 {576 throw!(RuntimeError(577 format!("%c expected 1 char string, got {}", s.chars().count()).into(),578 ));579 }580 tmp_out.push_str(&s);581 }582 _ => {583 throw!(TypeMismatch(584 "%c requires number/string",585 vec![ValType::Num, ValType::Str],586 value.value_type()?,587 ));588 }589 },590 ConvTypeV::Percent => tmp_out.push('%'),591 };592593 let padding = width.saturating_sub(tmp_out.len());594595 if !clfags.left {596 for _ in 0..padding {597 out.push(' ');598 }599 }600 out.push_str(&tmp_out);601 if clfags.left {602 for _ in 0..padding {603 out.push(' ');604 }605 }606607 Ok(())608}609610pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String> {611 let codes = parse_codes(str)?;612 let mut out = String::new();613614 for code in codes {615 match code {616 Element::String(s) => {617 out.push_str(s);618 }619 Element::Code(c) => {620 let width = match c.width {621 Width::Star => {622 if values.is_empty() {623 throw!(NotEnoughValues);624 }625 let value = &values[0];626 values = &values[1..];627 value.clone().try_cast_num("field width")? as usize628 }629 Width::Fixed(n) => n,630 };631 let precision = match c.precision {632 Some(Width::Star) => {633 if values.is_empty() {634 throw!(NotEnoughValues);635 }636 let value = &values[0];637 values = &values[1..];638 Some(value.clone().try_cast_num("field precision")? as usize)639 }640 Some(Width::Fixed(n)) => Some(n),641 None => None,642 };643644 645 let value = if c.convtype == ConvTypeV::Percent {646 &Val::Null647 } else {648 if values.is_empty() {649 throw!(NotEnoughValues);650 }651 let value = &values[0];652 values = &values[1..];653 value654 };655656 format_code(&mut out, value, &c, width, precision)?;657 }658 }659 }660661 Ok(out)662}663664pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {665 let codes = parse_codes(str)?;666 let mut out = String::new();667668 for code in codes {669 match code {670 Element::String(s) => {671 out.push_str(s);672 }673 Element::Code(c) => {674 675 let f: Rc<str> = c.mkey.into();676 let width = match c.width {677 Width::Star => {678 throw!(CannotUseStarWidthWithObject);679 }680 Width::Fixed(n) => n,681 };682 let precision = match c.precision {683 Some(Width::Star) => {684 throw!(CannotUseStarWidthWithObject);685 }686 Some(Width::Fixed(n)) => Some(n),687 None => None,688 };689690 let value = if c.convtype == ConvTypeV::Percent {691 Val::Null692 } else {693 if f.is_empty() {694 throw!(MappingKeysRequired);695 }696 if let Some(v) = values.get(f.clone())? {697 v698 } else {699 throw!(NoSuchFormatField(f));700 }701 };702703 format_code(&mut out, &value, &c, width, precision)?;704 }705 }706 }707708 Ok(out)709}710711#[cfg(test)]712pub mod test_format {713 use super::*;714715 #[test]716 fn parse() {717 assert_eq!(718 parse_codes(719 "How much error budget is left looking at our %.3f%% availability gurantees?"720 )721 .unwrap()722 .len(),723 4724 );725 }726727 #[test]728 fn octals() {729 assert_eq!(format_arr("%#o", &[Val::Num(8.0)]).unwrap(), "010");730 assert_eq!(format_arr("%#4o", &[Val::Num(8.0)]).unwrap(), " 010");731 assert_eq!(format_arr("%4o", &[Val::Num(8.0)]).unwrap(), " 10");732 assert_eq!(format_arr("%04o", &[Val::Num(8.0)]).unwrap(), "0010");733 assert_eq!(format_arr("%+4o", &[Val::Num(8.0)]).unwrap(), " +10");734 assert_eq!(format_arr("%+04o", &[Val::Num(8.0)]).unwrap(), "+010");735 assert_eq!(format_arr("%-4o", &[Val::Num(8.0)]).unwrap(), "10 ");736 assert_eq!(format_arr("%+-4o", &[Val::Num(8.0)]).unwrap(), "+10 ");737 assert_eq!(format_arr("%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");738 }739740 #[test]741 fn percent_doesnt_consumes_values() {742 assert_eq!(743 format_arr(744 "How much error budget is left looking at our %.3f%% availability gurantees?",745 &[Val::Num(4.0)]746 )747 .unwrap(),748 "How much error budget is left looking at our 4.000% availability gurantees?"749 );750 }751}