12#![allow(clippy::too_many_arguments)]34use gcmodule::Trace;5use jrsonnet_interner::IStr;6use jrsonnet_types::ValType;7use thiserror::Error;89use crate::{error::Error::*, throw, typed::Typed, LocError, ObjValue, Result, State, Val};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 s: State,466 out: &mut String,467 value: &Val,468 code: &Code,469 width: usize,470 precision: Option<usize>,471) -> Result<()> {472 let clfags = &code.cflags;473 let (fpprec, iprec) = match precision {474 Some(v) => (v, v),475 None => (6, 0),476 };477 let padding = if clfags.zero && !clfags.left {478 width479 } else {480 0481 };482483 484 let mut tmp_out = String::new();485486 match code.convtype {487 ConvTypeV::String => tmp_out.push_str(&value.clone().to_string(s)?),488 ConvTypeV::Decimal => {489 let value = f64::from_untyped(value.clone(), s)?;490 render_decimal(491 &mut tmp_out,492 value as i64,493 padding,494 iprec,495 clfags.blank,496 clfags.sign,497 );498 }499 ConvTypeV::Octal => {500 let value = f64::from_untyped(value.clone(), s)?;501 render_octal(502 &mut tmp_out,503 value as i64,504 padding,505 iprec,506 clfags.alt,507 clfags.blank,508 clfags.sign,509 );510 }511 ConvTypeV::Hexadecimal => {512 let value = f64::from_untyped(value.clone(), s)?;513 render_hexadecimal(514 &mut tmp_out,515 value as i64,516 padding,517 iprec,518 clfags.alt,519 clfags.blank,520 clfags.sign,521 code.caps,522 );523 }524 ConvTypeV::Scientific => {525 let value = f64::from_untyped(value.clone(), s)?;526 render_float_sci(527 &mut tmp_out,528 value,529 padding,530 fpprec,531 clfags.blank,532 clfags.sign,533 clfags.alt,534 true,535 code.caps,536 );537 }538 ConvTypeV::Float => {539 let value = f64::from_untyped(value.clone(), s)?;540 render_float(541 &mut tmp_out,542 value,543 padding,544 fpprec,545 clfags.blank,546 clfags.sign,547 clfags.alt,548 true,549 );550 }551 ConvTypeV::Shorter => {552 let value = f64::from_untyped(value.clone(), s)?;553 let exponent = value.log10().floor();554 if exponent < -4.0 || exponent >= fpprec as f64 {555 render_float_sci(556 &mut tmp_out,557 value,558 padding,559 fpprec - 1,560 clfags.blank,561 clfags.sign,562 clfags.alt,563 clfags.alt,564 code.caps,565 );566 } else {567 let digits_before_pt = 1.max(exponent as usize + 1);568 render_float(569 &mut tmp_out,570 value,571 padding,572 fpprec - digits_before_pt,573 clfags.blank,574 clfags.sign,575 clfags.alt,576 clfags.alt,577 );578 }579 }580 ConvTypeV::Char => match value.clone() {581 Val::Num(n) => tmp_out582 .push(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?),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(s: State, 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 usize::from_untyped(value.clone(), s.clone())?637 }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(usize::from_untyped(value.clone(), s.clone())?)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(s.clone(), &mut out, value, &c, width, precision)?;666 }667 }668 }669670 Ok(out)671}672673pub fn format_obj(s: State, 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(s.clone(), f.clone())? {706 v707 } else {708 throw!(NoSuchFormatField(f));709 }710 };711712 format_code(s.clone(), &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}