12#![allow(clippy::too_many_arguments)]34use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val};5use gcmodule::Trace;6use jrsonnet_interner::IStr;7use jrsonnet_types::ValType;8use std::convert::TryFrom;9use thiserror::Error;1011#[derive(Debug, Clone, Error, Trace)]12pub enum FormatError {13 #[error("truncated format code")]14 TruncatedFormatCode,15 #[error("unrecognized conversion type: {0}")]16 UnrecognizedConversionType(char),1718 #[error("not enough values")]19 NotEnoughValues,2021 #[error("cannot use * width with object")]22 CannotUseStarWidthWithObject,23 #[error("mapping keys required")]24 MappingKeysRequired,25 #[error("no such format field: {0}")]26 NoSuchFormatField(IStr),27}2829impl From<FormatError> for LocError {30 fn from(e: FormatError) -> Self {31 Self::new(Format(e))32 }33}3435use FormatError::*;3637type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;3839pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> {40 if str.is_empty() {41 return Err(TruncatedFormatCode);42 }43 let bytes = str.as_bytes();44 if bytes[0] == b'(' {45 let mut i = 1;46 while i < bytes.len() {47 if bytes[i] == b')' {48 return Ok((&str[1..i as usize], &str[i as usize + 1..]));49 }50 i += 1;51 }52 Err(TruncatedFormatCode)53 } else {54 Ok(("", str))55 }56}5758#[cfg(test)]59pub mod tests_key {60 use super::*;6162 #[test]63 fn parse_key() {64 assert_eq!(65 try_parse_mapping_key("(hello ) world").unwrap(),66 ("hello ", " world")67 );68 assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));69 assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));70 assert_eq!(71 try_parse_mapping_key(" () world").unwrap(),72 ("", " () world")73 );74 }7576 #[test]77 #[should_panic]78 fn parse_key_missing_start() {79 try_parse_mapping_key("").unwrap();80 }8182 #[test]83 #[should_panic]84 fn parse_key_missing_end() {85 try_parse_mapping_key("( ").unwrap();86 }87}8889#[derive(Default, Debug)]90pub struct CFlags {91 pub alt: bool,92 pub zero: bool,93 pub left: bool,94 pub blank: bool,95 pub sign: bool,96}9798pub fn try_parse_cflags(str: &str) -> ParseResult<CFlags> {99 if str.is_empty() {100 return Err(TruncatedFormatCode);101 }102 let bytes = str.as_bytes();103 let mut i = 0;104 let mut out = CFlags::default();105 loop {106 if bytes.len() == i {107 return Err(TruncatedFormatCode);108 }109 match bytes[i] {110 b'#' => out.alt = true,111 b'0' => out.zero = true,112 b'-' => out.left = true,113 b' ' => out.blank = true,114 b'+' => out.sign = true,115 _ => break,116 }117 i += 1;118 }119 Ok((out, &str[i..]))120}121122#[derive(Debug, PartialEq)]123pub enum Width {124 Star,125 Fixed(usize),126}127pub fn try_parse_field_width(str: &str) -> ParseResult<Width> {128 if str.is_empty() {129 return Err(TruncatedFormatCode);130 }131 let bytes = str.as_bytes();132 if bytes[0] == b'*' {133 return Ok((Width::Star, &str[1..]));134 }135 let mut out: usize = 0;136 let mut digits = 0;137 while let Some(digit) = (bytes[digits] as char).to_digit(10) {138 out *= 10;139 out += digit as usize;140 digits += 1;141 if digits == bytes.len() {142 return Err(TruncatedFormatCode);143 }144 }145 Ok((Width::Fixed(out), &str[digits..]))146}147148pub fn try_parse_precision(str: &str) -> ParseResult<Option<Width>> {149 if str.is_empty() {150 return Err(TruncatedFormatCode);151 }152 let bytes = str.as_bytes();153 if bytes[0] == b'.' {154 try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))155 } else {156 Ok((None, str))157 }158}159160161pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> {162 if str.is_empty() {163 return Err(TruncatedFormatCode);164 }165 let bytes = str.as_bytes();166 let mut idx = 0;167 while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {168 idx += 1;169 if bytes.len() == idx {170 return Err(TruncatedFormatCode);171 }172 }173 Ok(((), &str[idx..]))174}175176#[derive(Debug, PartialEq)]177pub enum ConvTypeV {178 Decimal,179 Octal,180 Hexadecimal,181 Scientific,182 Float,183 Shorter,184 Char,185 String,186 Percent,187}188pub struct ConvType {189 v: ConvTypeV,190 caps: bool,191}192193pub fn parse_conversion_type(str: &str) -> ParseResult<ConvType> {194 if str.is_empty() {195 return Err(TruncatedFormatCode);196 }197198 let code = str.as_bytes()[0];199 let v: (ConvTypeV, bool) = match code {200 b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),201 b'o' => (ConvTypeV::Octal, false),202 b'x' => (ConvTypeV::Hexadecimal, false),203 b'X' => (ConvTypeV::Hexadecimal, true),204 b'e' => (ConvTypeV::Scientific, false),205 b'E' => (ConvTypeV::Scientific, true),206 b'f' => (ConvTypeV::Float, false),207 b'F' => (ConvTypeV::Float, true),208 b'g' => (ConvTypeV::Shorter, false),209 b'G' => (ConvTypeV::Shorter, true),210 b'c' => (ConvTypeV::Char, false),211 b's' => (ConvTypeV::String, false),212 b'%' => (ConvTypeV::Percent, false),213 c => return Err(UnrecognizedConversionType(c as char)),214 };215216 Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))217}218219#[derive(Debug)]220pub struct Code<'s> {221 mkey: &'s str,222 cflags: CFlags,223 width: Width,224 precision: Option<Width>,225 convtype: ConvTypeV,226 caps: bool,227}228pub fn parse_code(str: &str) -> ParseResult<Code> {229 if str.is_empty() {230 return Err(TruncatedFormatCode);231 }232 let (mkey, str) = try_parse_mapping_key(str)?;233 let (cflags, str) = try_parse_cflags(str)?;234 let (width, str) = try_parse_field_width(str)?;235 let (precision, str) = try_parse_precision(str)?;236 let (_, str) = try_parse_length_modifier(str)?;237 let (convtype, str) = parse_conversion_type(str)?;238239 Ok((240 Code {241 mkey,242 cflags,243 width,244 precision,245 convtype: convtype.v,246 caps: convtype.caps,247 },248 str,249 ))250}251252#[derive(Debug)]253pub enum Element<'s> {254 String(&'s str),255 Code(Code<'s>),256}257pub fn parse_codes(mut str: &str) -> Result<Vec<Element>> {258 let mut bytes = str.as_bytes();259 let mut out = vec![];260 let mut offset = 0;261262 loop {263 while offset != bytes.len() && bytes[offset] != b'%' {264 offset += 1;265 }266 if offset != 0 {267 out.push(Element::String(&str[0..offset]));268 }269 if offset == bytes.len() {270 return Ok(out);271 }272 str = &str[offset + 1..];273 let (code, nstr) = parse_code(str)?;274 str = nstr;275 bytes = str.as_bytes();276 offset = 0;277278 out.push(Element::Code(code))279 }280}281282const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";283284#[inline]285pub fn render_integer(286 out: &mut String,287 iv: i64,288 padding: usize,289 precision: usize,290 blank: bool,291 sign: bool,292 radix: i64,293 prefix: &str,294 caps: bool,295) {296 297 298 let digits = if iv == 0 {299 vec![0u8]300 } else {301 let mut v = iv.abs();302 let mut nums = Vec::with_capacity(1);303 while v > 0 {304 nums.push((v % radix) as u8);305 v /= radix;306 }307 nums308 };309 let neg = iv < 0;310 let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });311 let zp2 = zp312 .max(precision)313 .saturating_sub(prefix.len() + digits.len());314315 if neg {316 out.push('-')317 } else if sign {318 out.push('+');319 } else if blank {320 out.push(' ');321 }322323 out.reserve(zp2);324 for _ in 0..zp2 {325 out.push('0');326 }327 out.push_str(prefix);328329 for digit in digits.into_iter().rev() {330 let ch = NUMBERS[digit as usize] as char;331 out.push(if caps { ch.to_ascii_uppercase() } else { ch });332 }333}334335pub fn render_decimal(336 out: &mut String,337 iv: i64,338 padding: usize,339 precision: usize,340 blank: bool,341 sign: bool,342) {343 render_integer(out, iv, padding, precision, blank, sign, 10, "", false)344}345pub fn render_octal(346 out: &mut String,347 iv: i64,348 padding: usize,349 precision: usize,350 alt: bool,351 blank: bool,352 sign: bool,353) {354 render_integer(355 out,356 iv,357 padding,358 precision,359 blank,360 sign,361 8,362 if alt && iv != 0 { "0" } else { "" },363 false,364 )365}366pub fn render_hexadecimal(367 out: &mut String,368 iv: i64,369 padding: usize,370 precision: usize,371 alt: bool,372 blank: bool,373 sign: bool,374 caps: bool,375) {376 render_integer(377 out,378 iv,379 padding,380 precision,381 blank,382 sign,383 16,384 match (alt, caps) {385 (true, true) => "0X",386 (true, false) => "0x",387 (false, _) => "",388 },389 caps,390 )391}392393pub fn render_float(394 out: &mut String,395 n: f64,396 mut padding: usize,397 precision: usize,398 blank: bool,399 sign: bool,400 ensure_pt: bool,401 trailing: bool,402) {403 let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };404 padding = padding.saturating_sub(dot_size + precision);405 render_decimal(out, n.floor() as i64, padding, 0, blank, sign);406 if precision == 0 {407 if ensure_pt {408 out.push('.');409 }410 return;411 }412 let frac = n413 .fract()414 .mul_add(10.0_f64.powf(precision as f64), 0.5)415 .floor();416 if trailing || frac > 0.0 {417 out.push('.');418 let mut frac_str = String::new();419 render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);420 let mut trim = frac_str.len();421 if !trailing {422 for b in frac_str.as_bytes().iter().rev() {423 if *b == b'0' {424 trim -= 1;425 }426 }427 }428 out.push_str(&frac_str[..trim]);429 } else if ensure_pt {430 out.push('.');431 }432}433434pub fn render_float_sci(435 out: &mut String,436 n: f64,437 mut padding: usize,438 precision: usize,439 blank: bool,440 sign: bool,441 ensure_pt: bool,442 trailing: bool,443 caps: bool,444) {445 let exponent = n.log10().floor();446 let mantissa = if exponent as i16 == -324 {447 n * 10.0 / 10.0_f64.powf(exponent + 1.0)448 } else {449 n / 10.0_f64.powf(exponent)450 };451 let mut exponent_str = String::new();452 render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);453454 455 padding = padding.saturating_sub(exponent_str.len() + 1);456457 render_float(458 out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,459 );460 out.push(if caps { 'E' } else { 'e' });461 out.push_str(&exponent_str);462}463464pub fn format_code(465 out: &mut String,466 value: &Val,467 code: &Code,468 width: usize,469 precision: Option<usize>,470) -> Result<()> {471 let clfags = &code.cflags;472 let (fpprec, iprec) = match precision {473 Some(v) => (v, v),474 None => (6, 0),475 };476 let padding = if clfags.zero && !clfags.left {477 width478 } else {479 0480 };481482 483 let mut tmp_out = String::new();484485 match code.convtype {486 ConvTypeV::String => tmp_out.push_str(&value.clone().to_string()?),487 ConvTypeV::Decimal => {488 let value = f64::try_from(value.clone())?;489 render_decimal(490 &mut tmp_out,491 value as i64,492 padding,493 iprec,494 clfags.blank,495 clfags.sign,496 );497 }498 ConvTypeV::Octal => {499 let value = f64::try_from(value.clone())?;500 render_octal(501 &mut tmp_out,502 value as i64,503 padding,504 iprec,505 clfags.alt,506 clfags.blank,507 clfags.sign,508 );509 }510 ConvTypeV::Hexadecimal => {511 let value = f64::try_from(value.clone())?;512 render_hexadecimal(513 &mut tmp_out,514 value as i64,515 padding,516 iprec,517 clfags.alt,518 clfags.blank,519 clfags.sign,520 code.caps,521 );522 }523 ConvTypeV::Scientific => {524 let value = f64::try_from(value.clone())?;525 render_float_sci(526 &mut tmp_out,527 value,528 padding,529 fpprec,530 clfags.blank,531 clfags.sign,532 clfags.alt,533 true,534 code.caps,535 );536 }537 ConvTypeV::Float => {538 let value = f64::try_from(value.clone())?;539 render_float(540 &mut tmp_out,541 value,542 padding,543 fpprec,544 clfags.blank,545 clfags.sign,546 clfags.alt,547 true,548 );549 }550 ConvTypeV::Shorter => {551 let value = f64::try_from(value.clone())?;552 let exponent = value.log10().floor();553 if exponent < -4.0 || exponent >= fpprec as f64 {554 render_float_sci(555 &mut tmp_out,556 value,557 padding,558 fpprec - 1,559 clfags.blank,560 clfags.sign,561 clfags.alt,562 clfags.alt,563 code.caps,564 );565 } else {566 let digits_before_pt = 1.max(exponent as usize + 1);567 render_float(568 &mut tmp_out,569 value,570 padding,571 fpprec - digits_before_pt,572 clfags.blank,573 clfags.sign,574 clfags.alt,575 clfags.alt,576 );577 }578 }579 ConvTypeV::Char => match value.clone() {580 Val::Num(n) => tmp_out581 .push(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?),582 Val::Str(s) => {583 if s.chars().count() != 1 {584 throw!(RuntimeError(585 format!("%c expected 1 char string, got {}", s.chars().count()).into(),586 ));587 }588 tmp_out.push_str(&s);589 }590 _ => {591 throw!(TypeMismatch(592 "%c requires number/string",593 vec![ValType::Num, ValType::Str],594 value.value_type(),595 ));596 }597 },598 ConvTypeV::Percent => tmp_out.push('%'),599 };600601 let padding = width.saturating_sub(tmp_out.len());602603 if !clfags.left {604 for _ in 0..padding {605 out.push(' ');606 }607 }608 out.push_str(&tmp_out);609 if clfags.left {610 for _ in 0..padding {611 out.push(' ');612 }613 }614615 Ok(())616}617618pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String> {619 let codes = parse_codes(str)?;620 let mut out = String::new();621622 for code in codes {623 match code {624 Element::String(s) => {625 out.push_str(s);626 }627 Element::Code(c) => {628 let width = match c.width {629 Width::Star => {630 if values.is_empty() {631 throw!(NotEnoughValues);632 }633 let value = &values[0];634 values = &values[1..];635 usize::try_from(value.clone())?636 }637 Width::Fixed(n) => n,638 };639 let precision = match c.precision {640 Some(Width::Star) => {641 if values.is_empty() {642 throw!(NotEnoughValues);643 }644 let value = &values[0];645 values = &values[1..];646 Some(usize::try_from(value.clone())?)647 }648 Some(Width::Fixed(n)) => Some(n),649 None => None,650 };651652 653 let value = if c.convtype == ConvTypeV::Percent {654 &Val::Null655 } else {656 if values.is_empty() {657 throw!(NotEnoughValues);658 }659 let value = &values[0];660 values = &values[1..];661 value662 };663664 format_code(&mut out, value, &c, width, precision)?;665 }666 }667 }668669 Ok(out)670}671672pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {673 let codes = parse_codes(str)?;674 let mut out = String::new();675676 for code in codes {677 match code {678 Element::String(s) => {679 out.push_str(s);680 }681 Element::Code(c) => {682 683 let f: IStr = c.mkey.into();684 let width = match c.width {685 Width::Star => {686 throw!(CannotUseStarWidthWithObject);687 }688 Width::Fixed(n) => n,689 };690 let precision = match c.precision {691 Some(Width::Star) => {692 throw!(CannotUseStarWidthWithObject);693 }694 Some(Width::Fixed(n)) => Some(n),695 None => None,696 };697698 let value = if c.convtype == ConvTypeV::Percent {699 Val::Null700 } else {701 if f.is_empty() {702 throw!(MappingKeysRequired);703 }704 if let Some(v) = values.get(f.clone())? {705 v706 } else {707 throw!(NoSuchFormatField(f));708 }709 };710711 format_code(&mut out, &value, &c, width, precision)?;712 }713 }714 }715716 Ok(out)717}718719#[cfg(test)]720pub mod test_format {721 use super::*;722723 #[test]724 fn parse() {725 assert_eq!(726 parse_codes(727 "How much error budget is left looking at our %.3f%% availability gurantees?"728 )729 .unwrap()730 .len(),731 4732 );733 }734735 #[test]736 fn octals() {737 assert_eq!(format_arr("%#o", &[Val::Num(8.0)]).unwrap(), "010");738 assert_eq!(format_arr("%#4o", &[Val::Num(8.0)]).unwrap(), " 010");739 assert_eq!(format_arr("%4o", &[Val::Num(8.0)]).unwrap(), " 10");740 assert_eq!(format_arr("%04o", &[Val::Num(8.0)]).unwrap(), "0010");741 assert_eq!(format_arr("%+4o", &[Val::Num(8.0)]).unwrap(), " +10");742 assert_eq!(format_arr("%+04o", &[Val::Num(8.0)]).unwrap(), "+010");743 assert_eq!(format_arr("%-4o", &[Val::Num(8.0)]).unwrap(), "10 ");744 assert_eq!(format_arr("%+-4o", &[Val::Num(8.0)]).unwrap(), "+10 ");745 assert_eq!(format_arr("%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");746 }747748 #[test]749 fn percent_doesnt_consumes_values() {750 assert_eq!(751 format_arr(752 "How much error budget is left looking at our %.3f%% availability gurantees?",753 &[Val::Num(4.0)]754 )755 .unwrap(),756 "How much error budget is left looking at our 4.000% availability gurantees?"757 );758 }759}