12#![allow(clippy::too_many_arguments)]34use crate::{5 create_error, create_error_result, to_string, Error, LocError, ObjValue, Val, ValType,6};78#[derive(Debug)]9pub enum FormatError {10 TruncatedFormatCode,11 UnrecognizedConversionType(char),12 ValueError(LocError),1314 NotEnoughValues,1516 CannotUseStarWidthWithObject,17 MappingKeysRequired,18 NoSuchField(Rc<str>),19}20impl From<LocError> for FormatError {21 fn from(e: LocError) -> Self {22 Self::ValueError(e)23 }24}25use std::rc::Rc;26use FormatError::*;2728pub fn try_parse_mapping_key(str: &str) -> Result<(&str, &str), FormatError> {29 if str.is_empty() {30 return Err(TruncatedFormatCode);31 }32 let bytes = str.as_bytes();33 if bytes[0] == b'(' {34 let mut i = 1;35 while i < bytes.len() {36 if bytes[i] == b')' {37 return Ok((&str[1..i as usize], &str[i as usize + 1..]));38 }39 i += 1;40 }41 Err(TruncatedFormatCode)42 } else {43 Ok(("", str))44 }45}4647#[cfg(test)]48pub mod tests_key {49 use super::*;5051 #[test]52 fn parse_key() {53 assert_eq!(54 try_parse_mapping_key("(hello ) world").unwrap(),55 ("hello ", " world")56 );57 assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));58 assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));59 assert_eq!(60 try_parse_mapping_key(" () world").unwrap(),61 ("", " () world")62 );63 }6465 #[test]66 #[should_panic]67 fn parse_key_missing_start() {68 try_parse_mapping_key("").unwrap();69 }7071 #[test]72 #[should_panic]73 fn parse_key_missing_end() {74 try_parse_mapping_key("( ").unwrap();75 }76}7778#[derive(Default, Debug)]79pub struct CFlags {80 pub alt: bool,81 pub zero: bool,82 pub left: bool,83 pub blank: bool,84 pub sign: bool,85}8687pub fn try_parse_cflags(str: &str) -> Result<(CFlags, &str), FormatError> {88 if str.is_empty() {89 return Err(TruncatedFormatCode);90 }91 let bytes = str.as_bytes();92 let mut i = 0;93 let mut out = CFlags::default();94 loop {95 if bytes.len() == i {96 return Err(TruncatedFormatCode);97 }98 match bytes[i] {99 b'#' => out.alt = true,100 b'0' => out.zero = true,101 b'-' => out.left = true,102 b' ' => out.blank = true,103 b'+' => out.sign = true,104 _ => break,105 }106 i += 1;107 }108 Ok((out, &str[i..]))109}110111#[derive(Debug, PartialEq)]112pub enum Width {113 Star,114 Fixed(usize),115}116pub fn try_parse_field_width(str: &str) -> Result<(Width, &str), FormatError> {117 if str.is_empty() {118 return Err(TruncatedFormatCode);119 }120 let bytes = str.as_bytes();121 if bytes[0] == b'*' {122 return Ok((Width::Star, &str[1..]));123 }124 let mut out: usize = 0;125 let mut digits = 0;126 while let Some(digit) = (bytes[digits] as char).to_digit(10) {127 out *= 10;128 out += digit as usize;129 digits += 1;130 if digits == bytes.len() {131 return Err(TruncatedFormatCode);132 }133 }134 Ok((Width::Fixed(out), &str[digits..]))135}136137pub fn try_parse_precision(str: &str) -> Result<(Option<Width>, &str), FormatError> {138 if str.is_empty() {139 return Err(TruncatedFormatCode);140 }141 let bytes = str.as_bytes();142 if bytes[0] == b'.' {143 try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))144 } else {145 Ok((None, str))146 }147}148149150pub fn try_parse_length_modifier(str: &str) -> Result<&str, FormatError> {151 if str.is_empty() {152 return Err(TruncatedFormatCode);153 }154 let bytes = str.as_bytes();155 let mut idx = 0;156 while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {157 idx += 1;158 if bytes.len() == idx {159 return Err(TruncatedFormatCode);160 }161 }162 Ok(&str[idx..])163}164165#[derive(Debug)]166pub enum ConvTypeV {167 Decimal,168 Octal,169 Hexadecimal,170 Scientific,171 Float,172 Shorter,173 Char,174 String,175 Percent,176}177pub struct ConvType {178 v: ConvTypeV,179 caps: bool,180}181182pub fn parse_conversion_type(str: &str) -> Result<(ConvType, &str), FormatError> {183 if str.is_empty() {184 return Err(TruncatedFormatCode);185 }186187 let code = str.as_bytes()[0];188 let v: (ConvTypeV, bool) = match code {189 b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),190 b'o' => (ConvTypeV::Octal, false),191 b'x' => (ConvTypeV::Hexadecimal, false),192 b'X' => (ConvTypeV::Hexadecimal, true),193 b'e' => (ConvTypeV::Scientific, false),194 b'E' => (ConvTypeV::Scientific, true),195 b'f' => (ConvTypeV::Float, false),196 b'F' => (ConvTypeV::Float, true),197 b'g' => (ConvTypeV::Shorter, false),198 b'G' => (ConvTypeV::Shorter, true),199 b'c' => (ConvTypeV::Char, false),200 b's' => (ConvTypeV::String, false),201 b'%' => (ConvTypeV::Percent, false),202 c => return Err(UnrecognizedConversionType(c as char)),203 };204205 Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))206}207208#[derive(Debug)]209pub struct Code<'s> {210 mkey: &'s str,211 cflags: CFlags,212 width: Width,213 precision: Option<Width>,214 convtype: ConvTypeV,215 caps: bool,216}217pub fn parse_code(str: &str) -> Result<(Code, &str), FormatError> {218 if str.is_empty() {219 return Err(TruncatedFormatCode);220 }221 let (mkey, str) = try_parse_mapping_key(str)?;222 let (cflags, str) = try_parse_cflags(str)?;223 let (width, str) = try_parse_field_width(str)?;224 let (precision, str) = try_parse_precision(str)?;225 let str = try_parse_length_modifier(str)?;226 let (convtype, str) = parse_conversion_type(str)?;227228 Ok((229 Code {230 mkey,231 cflags,232 width,233 precision,234 convtype: convtype.v,235 caps: convtype.caps,236 },237 str,238 ))239}240241#[derive(Debug)]242pub enum Element<'s> {243 String(&'s str),244 Code(Code<'s>),245}246pub fn parse_codes(mut str: &str) -> Result<Vec<Element>, FormatError> {247 let mut bytes = str.as_bytes();248 let mut out = vec![];249 let mut offset = 0;250251 loop {252 while offset != bytes.len() && bytes[offset] != b'%' {253 offset += 1;254 }255 if offset == bytes.len() {256 return Ok(out);257 }258 out.push(Element::String(&str[0..offset]));259 str = &str[offset + 1..];260 let (code, nstr) = parse_code(str)?;261 str = nstr;262 bytes = str.as_bytes();263 offset = 0;264265 out.push(Element::Code(code))266 }267}268269const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";270271#[inline]272pub fn render_integer(273 out: &mut String,274 iv: i64,275 padding: usize,276 precision: usize,277 blank: bool,278 sign: bool,279 radix: i64,280 prefix: &str,281 caps: bool,282) {283 284 285 let digits = if iv == 0 {286 vec![0u8]287 } else {288 let mut v = iv.abs();289 let mut nums = Vec::with_capacity(1);290 while v > 0 {291 nums.push((v % radix) as u8);292 v /= radix;293 }294 nums295 };296 let neg = iv < 0;297 let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });298 let zp2 = zp299 .max(precision)300 .saturating_sub(prefix.len() + digits.len());301302 if neg {303 out.push('-')304 } else if sign {305 out.push('+');306 } else if blank {307 out.push(' ');308 }309310 out.reserve(zp2);311 for _ in 0..zp2 {312 out.push('0');313 }314 out.push_str(&prefix);315316 for digit in digits.into_iter().rev() {317 let ch = NUMBERS[digit as usize] as char;318 out.push(if caps { ch.to_ascii_uppercase() } else { ch });319 }320}321322pub fn render_decimal(323 out: &mut String,324 iv: i64,325 padding: usize,326 precision: usize,327 blank: bool,328 sign: bool,329) {330 render_integer(out, iv, padding, precision, blank, sign, 10, "", false)331}332pub fn render_octal(333 out: &mut String,334 iv: i64,335 padding: usize,336 precision: usize,337 alt: bool,338 blank: bool,339 sign: bool,340) {341 render_integer(342 out,343 iv,344 padding,345 precision,346 blank,347 sign,348 8,349 if alt && iv != 0 { "0" } else { "" },350 false,351 )352}353pub fn render_hexadecimal(354 out: &mut String,355 iv: i64,356 padding: usize,357 precision: usize,358 alt: bool,359 blank: bool,360 sign: bool,361 caps: bool,362) {363 render_integer(364 out,365 iv,366 padding,367 precision,368 blank,369 sign,370 16,371 match (alt, caps) {372 (true, true) => "0X",373 (true, false) => "0x",374 (false, _) => "",375 },376 caps,377 )378}379380pub fn render_float(381 out: &mut String,382 n: f64,383 mut padding: usize,384 precision: usize,385 blank: bool,386 sign: bool,387 ensure_pt: bool,388 trailing: bool,389) {390 let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };391 padding = padding.saturating_sub(dot_size + precision);392 render_decimal(out, n.floor() as i64, padding, 0, blank, sign);393 if precision == 0 {394 if ensure_pt {395 out.push('.');396 }397 return;398 }399 let frac = (n.fract() * 10.0_f64.powf(precision as f64) + 0.5).floor();400 if trailing || frac > 0.0 {401 out.push('.');402 let mut frac_str = String::new();403 render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);404 let mut trim = frac_str.len();405 if !trailing {406 for b in frac_str.as_bytes().iter().rev() {407 if *b == b'0' {408 trim -= 1;409 }410 }411 }412 out.push_str(&frac_str[..trim]);413 } else if ensure_pt {414 out.push('.');415 }416}417418pub fn render_float_sci(419 out: &mut String,420 n: f64,421 mut padding: usize,422 precision: usize,423 blank: bool,424 sign: bool,425 ensure_pt: bool,426 trailing: bool,427 caps: bool,428) {429 let exponent = n.log10().floor();430 let mantissa = if exponent as i16 == -324 {431 n * 10.0 / 10.0_f64.powf(exponent + 1.0)432 } else {433 n / 10.0_f64.powf(exponent)434 };435 let mut exponent_str = String::new();436 render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);437438 439 padding = padding.saturating_sub(exponent_str.len() + 1);440441 render_float(442 out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,443 );444 out.push(if caps { 'E' } else { 'e' });445 out.push_str(&exponent_str);446}447448pub fn format_code(449 out: &mut String,450 value: &Val,451 code: &Code,452 width: usize,453 precision: Option<usize>,454) -> Result<(), FormatError> {455 let clfags = &code.cflags;456 let (fpprec, iprec) = match precision {457 Some(v) => (v, v),458 None => (6, 0),459 };460 let padding = if clfags.zero && !clfags.left {461 width462 } else {463 0464 };465466 467 let mut tmp_out = String::new();468469 match code.convtype {470 ConvTypeV::String => tmp_out.push_str(&to_string(value)?),471 ConvTypeV::Decimal => {472 let value = value.clone().try_cast_num("%d/%u/%i requires number")?;473 render_decimal(474 &mut tmp_out,475 value as i64,476 padding,477 iprec,478 clfags.blank,479 clfags.sign,480 );481 }482 ConvTypeV::Octal => {483 let value = value.clone().try_cast_num("%o requires number")?;484 render_octal(485 &mut tmp_out,486 value as i64,487 padding,488 iprec,489 clfags.alt,490 clfags.blank,491 clfags.sign,492 );493 }494 ConvTypeV::Hexadecimal => {495 let value = value.clone().try_cast_num("%x/%X requires number")?;496 render_hexadecimal(497 &mut tmp_out,498 value as i64,499 padding,500 iprec,501 clfags.alt,502 clfags.blank,503 clfags.sign,504 code.caps,505 );506 }507 ConvTypeV::Scientific => {508 let value = value.clone().try_cast_num("%e/%E requires number")?;509 render_float_sci(510 &mut tmp_out,511 value,512 padding,513 fpprec,514 clfags.blank,515 clfags.sign,516 clfags.alt,517 true,518 code.caps,519 );520 }521 ConvTypeV::Float => {522 let value = value.clone().try_cast_num("%e/%E requires number")?;523 render_float(524 &mut tmp_out,525 value,526 padding,527 fpprec,528 clfags.blank,529 clfags.sign,530 clfags.alt,531 true,532 );533 }534 ConvTypeV::Shorter => {535 let value = value.clone().try_cast_num("%g/%G requires number")?;536 let exponent = value.log10().floor();537 if exponent < -4.0 || exponent >= fpprec as f64 {538 render_float_sci(539 &mut tmp_out,540 value,541 padding,542 fpprec - 1,543 clfags.blank,544 clfags.sign,545 clfags.alt,546 clfags.alt,547 code.caps,548 );549 } else {550 let digits_before_pt = 1.max(exponent as usize + 1);551 render_float(552 &mut tmp_out,553 value,554 padding,555 fpprec - digits_before_pt,556 clfags.blank,557 clfags.sign,558 clfags.alt,559 clfags.alt,560 );561 }562 }563 ConvTypeV::Char => match value.clone().unwrap_if_lazy()? {564 Val::Num(n) => tmp_out.push(565 std::char::from_u32(n as u32)566 .ok_or_else(|| create_error(Error::InvalidUnicodeCodepointGot(n as u32)))?,567 ),568 Val::Str(s) => {569 if s.chars().count() != 1 {570 create_error_result(Error::RuntimeError(571 format!("%c expected 1 char string, got {}", s.chars().count()).into(),572 ))?;573 }574 tmp_out.push_str(&s);575 }576 _ => {577 create_error_result(Error::TypeMismatch(578 "%c requires number/string",579 vec![ValType::Num, ValType::Str],580 value.value_type()?,581 ))?;582 }583 },584 ConvTypeV::Percent => tmp_out.push('%'),585 };586587 let padding = width.saturating_sub(tmp_out.len());588589 if !clfags.left {590 for _ in 0..padding {591 out.push(' ');592 }593 }594 out.push_str(&tmp_out);595 if clfags.left {596 for _ in 0..padding {597 out.push(' ');598 }599 }600601 Ok(())602}603604pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String, FormatError> {605 let codes = parse_codes(&str)?;606 let mut out = String::new();607608 for code in codes {609 match code {610 Element::String(s) => {611 out.push_str(s);612 }613 Element::Code(c) => {614 let width = match c.width {615 Width::Star => {616 if values.is_empty() {617 return Err(FormatError::NotEnoughValues);618 }619 let value = &values[0];620 values = &values[1..];621 value.clone().try_cast_num("field width")? as usize622 }623 Width::Fixed(n) => n,624 };625 let precision = match c.precision {626 Some(Width::Star) => {627 if values.is_empty() {628 return Err(FormatError::NotEnoughValues);629 }630 let value = &values[0];631 values = &values[1..];632 Some(value.clone().try_cast_num("field precision")? as usize)633 }634 Some(Width::Fixed(n)) => Some(n),635 None => None,636 };637 if values.is_empty() {638 return Err(FormatError::NotEnoughValues);639 }640 let value = &values[0];641 values = &values[1..];642643 format_code(&mut out, value, &c, width, precision)?;644 }645 }646 }647648 Ok(out)649}650651pub fn format_obj(str: &str, values: &ObjValue) -> Result<String, FormatError> {652 let codes = parse_codes(&str)?;653 let mut out = String::new();654655 for code in codes {656 match code {657 Element::String(s) => {658 out.push_str(s);659 }660 Element::Code(c) => {661 662 let f: Rc<str> = c.mkey.into();663 if f.is_empty() {664 return Err(FormatError::MappingKeysRequired);665 }666 let width = match c.width {667 Width::Star => {668 return Err(FormatError::CannotUseStarWidthWithObject);669 }670 Width::Fixed(n) => n,671 };672 let precision = match c.precision {673 Some(Width::Star) => {674 return Err(FormatError::CannotUseStarWidthWithObject);675 }676 Some(Width::Fixed(n)) => Some(n),677 None => None,678 };679 let value = if let Some(v) = values.get(f.clone())? {680 v681 } else {682 return Err(FormatError::NoSuchField(f));683 };684685 format_code(&mut out, &value, &c, width, precision)?;686 }687 }688 }689690 Ok(out)691}692693#[cfg(test)]694pub mod test_format {695 use super::*;696697 #[test]698 fn parse() {699 println!("{:?}", parse_codes("Hello %s world!!! %s %(aaa)s ww"));700 }701702 #[test]703 fn octals() {704 assert_eq!(format_arr("%#o", &[Val::Num(8.0)]).unwrap(), "010");705 assert_eq!(format_arr("%#4o", &[Val::Num(8.0)]).unwrap(), " 010");706 assert_eq!(format_arr("%4o", &[Val::Num(8.0)]).unwrap(), " 10");707 assert_eq!(format_arr("%04o", &[Val::Num(8.0)]).unwrap(), "0010");708 assert_eq!(format_arr("%+4o", &[Val::Num(8.0)]).unwrap(), " +10");709 assert_eq!(format_arr("%+04o", &[Val::Num(8.0)]).unwrap(), "+010");710 assert_eq!(format_arr("%-4o", &[Val::Num(8.0)]).unwrap(), "10 ");711 assert_eq!(format_arr("%+-4o", &[Val::Num(8.0)]).unwrap(), "+10 ");712 assert_eq!(format_arr("%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");713 }714}