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 thiserror::Error;910#[derive(Debug, Clone, Error, Trace)]11pub enum FormatError {12 #[error("truncated format code")]13 TruncatedFormatCode,14 #[error("unrecognized conversion type: {0}")]15 UnrecognizedConversionType(char),1617 #[error("not enough values")]18 NotEnoughValues,1920 #[error("cannot use * width with object")]21 CannotUseStarWidthWithObject,22 #[error("mapping keys required")]23 MappingKeysRequired,24 #[error("no such format field: {0}")]25 NoSuchFormatField(IStr),26}2728impl From<FormatError> for LocError {29 fn from(e: FormatError) -> Self {30 Self::new(Format(e))31 }32}3334use FormatError::*;3536type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;3738pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> {39 if str.is_empty() {40 return Err(TruncatedFormatCode);41 }42 let bytes = str.as_bytes();43 if bytes[0] == b'(' {44 let mut i = 1;45 while i < bytes.len() {46 if bytes[i] == b')' {47 return Ok((&str[1..i as usize], &str[i as usize + 1..]));48 }49 i += 1;50 }51 Err(TruncatedFormatCode)52 } else {53 Ok(("", str))54 }55}5657#[cfg(test)]58pub mod tests_key {59 use super::*;6061 #[test]62 fn parse_key() {63 assert_eq!(64 try_parse_mapping_key("(hello ) world").unwrap(),65 ("hello ", " world")66 );67 assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));68 assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));69 assert_eq!(70 try_parse_mapping_key(" () world").unwrap(),71 ("", " () world")72 );73 }7475 #[test]76 #[should_panic]77 fn parse_key_missing_start() {78 try_parse_mapping_key("").unwrap();79 }8081 #[test]82 #[should_panic]83 fn parse_key_missing_end() {84 try_parse_mapping_key("( ").unwrap();85 }86}8788#[derive(Default, Debug)]89pub struct CFlags {90 pub alt: bool,91 pub zero: bool,92 pub left: bool,93 pub blank: bool,94 pub sign: bool,95}9697pub fn try_parse_cflags(str: &str) -> ParseResult<CFlags> {98 if str.is_empty() {99 return Err(TruncatedFormatCode);100 }101 let bytes = str.as_bytes();102 let mut i = 0;103 let mut out = CFlags::default();104 loop {105 if bytes.len() == i {106 return Err(TruncatedFormatCode);107 }108 match bytes[i] {109 b'#' => out.alt = true,110 b'0' => out.zero = true,111 b'-' => out.left = true,112 b' ' => out.blank = true,113 b'+' => out.sign = true,114 _ => break,115 }116 i += 1;117 }118 Ok((out, &str[i..]))119}120121#[derive(Debug, PartialEq)]122pub enum Width {123 Star,124 Fixed(usize),125}126pub fn try_parse_field_width(str: &str) -> ParseResult<Width> {127 if str.is_empty() {128 return Err(TruncatedFormatCode);129 }130 let bytes = str.as_bytes();131 if bytes[0] == b'*' {132 return Ok((Width::Star, &str[1..]));133 }134 let mut out: usize = 0;135 let mut digits = 0;136 while let Some(digit) = (bytes[digits] as char).to_digit(10) {137 out *= 10;138 out += digit as usize;139 digits += 1;140 if digits == bytes.len() {141 return Err(TruncatedFormatCode);142 }143 }144 Ok((Width::Fixed(out), &str[digits..]))145}146147pub fn try_parse_precision(str: &str) -> ParseResult<Option<Width>> {148 if str.is_empty() {149 return Err(TruncatedFormatCode);150 }151 let bytes = str.as_bytes();152 if bytes[0] == b'.' {153 try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))154 } else {155 Ok((None, str))156 }157}158159160pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> {161 if str.is_empty() {162 return Err(TruncatedFormatCode);163 }164 let bytes = str.as_bytes();165 let mut idx = 0;166 while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {167 idx += 1;168 if bytes.len() == idx {169 return Err(TruncatedFormatCode);170 }171 }172 Ok(((), &str[idx..]))173}174175#[derive(Debug, PartialEq)]176pub enum ConvTypeV {177 Decimal,178 Octal,179 Hexadecimal,180 Scientific,181 Float,182 Shorter,183 Char,184 String,185 Percent,186}187pub struct ConvType {188 v: ConvTypeV,189 caps: bool,190}191192pub fn parse_conversion_type(str: &str) -> ParseResult<ConvType> {193 if str.is_empty() {194 return Err(TruncatedFormatCode);195 }196197 let code = str.as_bytes()[0];198 let v: (ConvTypeV, bool) = match code {199 b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),200 b'o' => (ConvTypeV::Octal, false),201 b'x' => (ConvTypeV::Hexadecimal, false),202 b'X' => (ConvTypeV::Hexadecimal, true),203 b'e' => (ConvTypeV::Scientific, false),204 b'E' => (ConvTypeV::Scientific, true),205 b'f' => (ConvTypeV::Float, false),206 b'F' => (ConvTypeV::Float, true),207 b'g' => (ConvTypeV::Shorter, false),208 b'G' => (ConvTypeV::Shorter, true),209 b'c' => (ConvTypeV::Char, false),210 b's' => (ConvTypeV::String, false),211 b'%' => (ConvTypeV::Percent, false),212 c => return Err(UnrecognizedConversionType(c as char)),213 };214215 Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))216}217218#[derive(Debug)]219pub struct Code<'s> {220 mkey: &'s str,221 cflags: CFlags,222 width: Width,223 precision: Option<Width>,224 convtype: ConvTypeV,225 caps: bool,226}227pub fn parse_code(str: &str) -> ParseResult<Code> {228 if str.is_empty() {229 return Err(TruncatedFormatCode);230 }231 let (mkey, str) = try_parse_mapping_key(str)?;232 let (cflags, str) = try_parse_cflags(str)?;233 let (width, str) = try_parse_field_width(str)?;234 let (precision, str) = try_parse_precision(str)?;235 let (_, str) = try_parse_length_modifier(str)?;236 let (convtype, str) = parse_conversion_type(str)?;237238 Ok((239 Code {240 mkey,241 cflags,242 width,243 precision,244 convtype: convtype.v,245 caps: convtype.caps,246 },247 str,248 ))249}250251#[derive(Debug)]252pub enum Element<'s> {253 String(&'s str),254 Code(Code<'s>),255}256pub fn parse_codes(mut str: &str) -> Result<Vec<Element>> {257 let mut bytes = str.as_bytes();258 let mut out = vec![];259 let mut offset = 0;260261 loop {262 while offset != bytes.len() && bytes[offset] != b'%' {263 offset += 1;264 }265 if offset != 0 {266 out.push(Element::String(&str[0..offset]));267 }268 if offset == bytes.len() {269 return Ok(out);270 }271 str = &str[offset + 1..];272 let (code, nstr) = parse_code(str)?;273 str = nstr;274 bytes = str.as_bytes();275 offset = 0;276277 out.push(Element::Code(code))278 }279}280281const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";282283#[inline]284pub fn render_integer(285 out: &mut String,286 iv: i64,287 padding: usize,288 precision: usize,289 blank: bool,290 sign: bool,291 radix: i64,292 prefix: &str,293 caps: bool,294) {295 296 297 let digits = if iv == 0 {298 vec![0u8]299 } else {300 let mut v = iv.abs();301 let mut nums = Vec::with_capacity(1);302 while v > 0 {303 nums.push((v % radix) as u8);304 v /= radix;305 }306 nums307 };308 let neg = iv < 0;309 let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });310 let zp2 = zp311 .max(precision)312 .saturating_sub(prefix.len() + digits.len());313314 if neg {315 out.push('-')316 } else if sign {317 out.push('+');318 } else if blank {319 out.push(' ');320 }321322 out.reserve(zp2);323 for _ in 0..zp2 {324 out.push('0');325 }326 out.push_str(prefix);327328 for digit in digits.into_iter().rev() {329 let ch = NUMBERS[digit as usize] as char;330 out.push(if caps { ch.to_ascii_uppercase() } else { ch });331 }332}333334pub fn render_decimal(335 out: &mut String,336 iv: i64,337 padding: usize,338 precision: usize,339 blank: bool,340 sign: bool,341) {342 render_integer(out, iv, padding, precision, blank, sign, 10, "", false)343}344pub fn render_octal(345 out: &mut String,346 iv: i64,347 padding: usize,348 precision: usize,349 alt: bool,350 blank: bool,351 sign: bool,352) {353 render_integer(354 out,355 iv,356 padding,357 precision,358 blank,359 sign,360 8,361 if alt && iv != 0 { "0" } else { "" },362 false,363 )364}365pub fn render_hexadecimal(366 out: &mut String,367 iv: i64,368 padding: usize,369 precision: usize,370 alt: bool,371 blank: bool,372 sign: bool,373 caps: bool,374) {375 render_integer(376 out,377 iv,378 padding,379 precision,380 blank,381 sign,382 16,383 match (alt, caps) {384 (true, true) => "0X",385 (true, false) => "0x",386 (false, _) => "",387 },388 caps,389 )390}391392pub fn render_float(393 out: &mut String,394 n: f64,395 mut padding: usize,396 precision: usize,397 blank: bool,398 sign: bool,399 ensure_pt: bool,400 trailing: bool,401) {402 let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };403 padding = padding.saturating_sub(dot_size + precision);404 render_decimal(out, n.floor() as i64, padding, 0, blank, sign);405 if precision == 0 {406 if ensure_pt {407 out.push('.');408 }409 return;410 }411 let frac = n412 .fract()413 .mul_add(10.0_f64.powf(precision as f64), 0.5)414 .floor();415 if trailing || frac > 0.0 {416 out.push('.');417 let mut frac_str = String::new();418 render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);419 let mut trim = frac_str.len();420 if !trailing {421 for b in frac_str.as_bytes().iter().rev() {422 if *b == b'0' {423 trim -= 1;424 }425 }426 }427 out.push_str(&frac_str[..trim]);428 } else if ensure_pt {429 out.push('.');430 }431}432433pub fn render_float_sci(434 out: &mut String,435 n: f64,436 mut padding: usize,437 precision: usize,438 blank: bool,439 sign: bool,440 ensure_pt: bool,441 trailing: bool,442 caps: bool,443) {444 let exponent = n.log10().floor();445 let mantissa = if exponent as i16 == -324 {446 n * 10.0 / 10.0_f64.powf(exponent + 1.0)447 } else {448 n / 10.0_f64.powf(exponent)449 };450 let mut exponent_str = String::new();451 render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);452453 454 padding = padding.saturating_sub(exponent_str.len() + 1);455456 render_float(457 out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,458 );459 out.push(if caps { 'E' } else { 'e' });460 out.push_str(&exponent_str);461}462463pub fn format_code(464 out: &mut String,465 value: &Val,466 code: &Code,467 width: usize,468 precision: Option<usize>,469) -> Result<()> {470 let clfags = &code.cflags;471 let (fpprec, iprec) = match precision {472 Some(v) => (v, v),473 None => (6, 0),474 };475 let padding = if clfags.zero && !clfags.left {476 width477 } else {478 0479 };480481 482 let mut tmp_out = String::new();483484 match code.convtype {485 ConvTypeV::String => tmp_out.push_str(&value.clone().to_string()?),486 ConvTypeV::Decimal => {487 let value = value.clone().try_cast_num("%d/%u/%i requires number")?;488 render_decimal(489 &mut tmp_out,490 value as i64,491 padding,492 iprec,493 clfags.blank,494 clfags.sign,495 );496 }497 ConvTypeV::Octal => {498 let value = value.clone().try_cast_num("%o requires number")?;499 render_octal(500 &mut tmp_out,501 value as i64,502 padding,503 iprec,504 clfags.alt,505 clfags.blank,506 clfags.sign,507 );508 }509 ConvTypeV::Hexadecimal => {510 let value = value.clone().try_cast_num("%x/%X requires number")?;511 render_hexadecimal(512 &mut tmp_out,513 value as i64,514 padding,515 iprec,516 clfags.alt,517 clfags.blank,518 clfags.sign,519 code.caps,520 );521 }522 ConvTypeV::Scientific => {523 let value = value.clone().try_cast_num("%e/%E requires number")?;524 render_float_sci(525 &mut tmp_out,526 value,527 padding,528 fpprec,529 clfags.blank,530 clfags.sign,531 clfags.alt,532 true,533 code.caps,534 );535 }536 ConvTypeV::Float => {537 let value = value.clone().try_cast_num("%e/%E requires number")?;538 render_float(539 &mut tmp_out,540 value,541 padding,542 fpprec,543 clfags.blank,544 clfags.sign,545 clfags.alt,546 true,547 );548 }549 ConvTypeV::Shorter => {550 let value = value.clone().try_cast_num("%g/%G requires number")?;551 let exponent = value.log10().floor();552 if exponent < -4.0 || exponent >= fpprec as f64 {553 render_float_sci(554 &mut tmp_out,555 value,556 padding,557 fpprec - 1,558 clfags.blank,559 clfags.sign,560 clfags.alt,561 clfags.alt,562 code.caps,563 );564 } else {565 let digits_before_pt = 1.max(exponent as usize + 1);566 render_float(567 &mut tmp_out,568 value,569 padding,570 fpprec - digits_before_pt,571 clfags.blank,572 clfags.sign,573 clfags.alt,574 clfags.alt,575 );576 }577 }578 ConvTypeV::Char => match value.clone() {579 Val::Num(n) => tmp_out.push(580 std::char::from_u32(n as u32)581 .ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,582 ),583 Val::Str(s) => {584 if s.chars().count() != 1 {585 throw!(RuntimeError(586 format!("%c expected 1 char string, got {}", s.chars().count()).into(),587 ));588 }589 tmp_out.push_str(&s);590 }591 _ => {592 throw!(TypeMismatch(593 "%c requires number/string",594 vec![ValType::Num, ValType::Str],595 value.value_type(),596 ));597 }598 },599 ConvTypeV::Percent => tmp_out.push('%'),600 };601602 let padding = width.saturating_sub(tmp_out.len());603604 if !clfags.left {605 for _ in 0..padding {606 out.push(' ');607 }608 }609 out.push_str(&tmp_out);610 if clfags.left {611 for _ in 0..padding {612 out.push(' ');613 }614 }615616 Ok(())617}618619pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String> {620 let codes = parse_codes(str)?;621 let mut out = String::new();622623 for code in codes {624 match code {625 Element::String(s) => {626 out.push_str(s);627 }628 Element::Code(c) => {629 let width = match c.width {630 Width::Star => {631 if values.is_empty() {632 throw!(NotEnoughValues);633 }634 let value = &values[0];635 values = &values[1..];636 value.clone().try_cast_num("field width")? as usize637 }638 Width::Fixed(n) => n,639 };640 let precision = match c.precision {641 Some(Width::Star) => {642 if values.is_empty() {643 throw!(NotEnoughValues);644 }645 let value = &values[0];646 values = &values[1..];647 Some(value.clone().try_cast_num("field precision")? as usize)648 }649 Some(Width::Fixed(n)) => Some(n),650 None => None,651 };652653 654 let value = if c.convtype == ConvTypeV::Percent {655 &Val::Null656 } else {657 if values.is_empty() {658 throw!(NotEnoughValues);659 }660 let value = &values[0];661 values = &values[1..];662 value663 };664665 format_code(&mut out, value, &c, width, precision)?;666 }667 }668 }669670 Ok(out)671}672673pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {674 let codes = parse_codes(str)?;675 let mut out = String::new();676677 for code in codes {678 match code {679 Element::String(s) => {680 out.push_str(s);681 }682 Element::Code(c) => {683 684 let f: IStr = c.mkey.into();685 let width = match c.width {686 Width::Star => {687 throw!(CannotUseStarWidthWithObject);688 }689 Width::Fixed(n) => n,690 };691 let precision = match c.precision {692 Some(Width::Star) => {693 throw!(CannotUseStarWidthWithObject);694 }695 Some(Width::Fixed(n)) => Some(n),696 None => None,697 };698699 let value = if c.convtype == ConvTypeV::Percent {700 Val::Null701 } else {702 if f.is_empty() {703 throw!(MappingKeysRequired);704 }705 if let Some(v) = values.get(f.clone())? {706 v707 } else {708 throw!(NoSuchFormatField(f));709 }710 };711712 format_code(&mut out, &value, &c, width, precision)?;713 }714 }715 }716717 Ok(out)718}719720#[cfg(test)]721pub mod test_format {722 use super::*;723724 #[test]725 fn parse() {726 assert_eq!(727 parse_codes(728 "How much error budget is left looking at our %.3f%% availability gurantees?"729 )730 .unwrap()731 .len(),732 4733 );734 }735736 #[test]737 fn octals() {738 assert_eq!(format_arr("%#o", &[Val::Num(8.0)]).unwrap(), "010");739 assert_eq!(format_arr("%#4o", &[Val::Num(8.0)]).unwrap(), " 010");740 assert_eq!(format_arr("%4o", &[Val::Num(8.0)]).unwrap(), " 10");741 assert_eq!(format_arr("%04o", &[Val::Num(8.0)]).unwrap(), "0010");742 assert_eq!(format_arr("%+4o", &[Val::Num(8.0)]).unwrap(), " +10");743 assert_eq!(format_arr("%+04o", &[Val::Num(8.0)]).unwrap(), "+010");744 assert_eq!(format_arr("%-4o", &[Val::Num(8.0)]).unwrap(), "10 ");745 assert_eq!(format_arr("%+-4o", &[Val::Num(8.0)]).unwrap(), "+10 ");746 assert_eq!(format_arr("%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");747 }748749 #[test]750 fn percent_doesnt_consumes_values() {751 assert_eq!(752 format_arr(753 "How much error budget is left looking at our %.3f%% availability gurantees?",754 &[Val::Num(4.0)]755 )756 .unwrap(),757 "How much error budget is left looking at our 4.000% availability gurantees?"758 );759 }760}