git.delta.rocks / jrsonnet / refs/commits / e53349155ac3

difftreelog

source

crates/jrsonnet-evaluator/src/builtin/format.rs15.3 KiBsourcehistory
1//! faster std.format impl2#![allow(clippy::too_many_arguments)]34use std::convert::TryFrom;56use gcmodule::Trace;7use jrsonnet_interner::IStr;8use jrsonnet_types::ValType;9use thiserror::Error;1011use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val};1213#[derive(Debug, Clone, Error, Trace)]14pub enum FormatError {15	#[error("truncated format code")]16	TruncatedFormatCode,17	#[error("unrecognized conversion type: {0}")]18	UnrecognizedConversionType(char),1920	#[error("not enough values")]21	NotEnoughValues,2223	#[error("cannot use * width with object")]24	CannotUseStarWidthWithObject,25	#[error("mapping keys required")]26	MappingKeysRequired,27	#[error("no such format field: {0}")]28	NoSuchFormatField(IStr),29}3031impl From<FormatError> for LocError {32	fn from(e: FormatError) -> Self {33		Self::new(Format(e))34	}35}3637use FormatError::*;3839type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;4041pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> {42	if str.is_empty() {43		return Err(TruncatedFormatCode);44	}45	let bytes = str.as_bytes();46	if bytes[0] == b'(' {47		let mut i = 1;48		while i < bytes.len() {49			if bytes[i] == b')' {50				return Ok((&str[1..i as usize], &str[i as usize + 1..]));51			}52			i += 1;53		}54		Err(TruncatedFormatCode)55	} else {56		Ok(("", str))57	}58}5960#[cfg(test)]61pub mod tests_key {62	use super::*;6364	#[test]65	fn parse_key() {66		assert_eq!(67			try_parse_mapping_key("(hello ) world").unwrap(),68			("hello ", " world")69		);70		assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));71		assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));72		assert_eq!(73			try_parse_mapping_key(" () world").unwrap(),74			("", " () world")75		);76	}7778	#[test]79	#[should_panic]80	fn parse_key_missing_start() {81		try_parse_mapping_key("").unwrap();82	}8384	#[test]85	#[should_panic]86	fn parse_key_missing_end() {87		try_parse_mapping_key("(   ").unwrap();88	}89}9091#[derive(Default, Debug)]92pub struct CFlags {93	pub alt: bool,94	pub zero: bool,95	pub left: bool,96	pub blank: bool,97	pub sign: bool,98}99100pub fn try_parse_cflags(str: &str) -> ParseResult<CFlags> {101	if str.is_empty() {102		return Err(TruncatedFormatCode);103	}104	let bytes = str.as_bytes();105	let mut i = 0;106	let mut out = CFlags::default();107	loop {108		if bytes.len() == i {109			return Err(TruncatedFormatCode);110		}111		match bytes[i] {112			b'#' => out.alt = true,113			b'0' => out.zero = true,114			b'-' => out.left = true,115			b' ' => out.blank = true,116			b'+' => out.sign = true,117			_ => break,118		}119		i += 1;120	}121	Ok((out, &str[i..]))122}123124#[derive(Debug, PartialEq)]125pub enum Width {126	Star,127	Fixed(usize),128}129pub fn try_parse_field_width(str: &str) -> ParseResult<Width> {130	if str.is_empty() {131		return Err(TruncatedFormatCode);132	}133	let bytes = str.as_bytes();134	if bytes[0] == b'*' {135		return Ok((Width::Star, &str[1..]));136	}137	let mut out: usize = 0;138	let mut digits = 0;139	while let Some(digit) = (bytes[digits] as char).to_digit(10) {140		out *= 10;141		out += digit as usize;142		digits += 1;143		if digits == bytes.len() {144			return Err(TruncatedFormatCode);145		}146	}147	Ok((Width::Fixed(out), &str[digits..]))148}149150pub fn try_parse_precision(str: &str) -> ParseResult<Option<Width>> {151	if str.is_empty() {152		return Err(TruncatedFormatCode);153	}154	let bytes = str.as_bytes();155	if bytes[0] == b'.' {156		try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))157	} else {158		Ok((None, str))159	}160}161162// Only skips163pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> {164	if str.is_empty() {165		return Err(TruncatedFormatCode);166	}167	let bytes = str.as_bytes();168	let mut idx = 0;169	while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {170		idx += 1;171		if bytes.len() == idx {172			return Err(TruncatedFormatCode);173		}174	}175	Ok(((), &str[idx..]))176}177178#[derive(Debug, PartialEq)]179pub enum ConvTypeV {180	Decimal,181	Octal,182	Hexadecimal,183	Scientific,184	Float,185	Shorter,186	Char,187	String,188	Percent,189}190pub struct ConvType {191	v: ConvTypeV,192	caps: bool,193}194195pub fn parse_conversion_type(str: &str) -> ParseResult<ConvType> {196	if str.is_empty() {197		return Err(TruncatedFormatCode);198	}199200	let code = str.as_bytes()[0];201	let v: (ConvTypeV, bool) = match code {202		b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),203		b'o' => (ConvTypeV::Octal, false),204		b'x' => (ConvTypeV::Hexadecimal, false),205		b'X' => (ConvTypeV::Hexadecimal, true),206		b'e' => (ConvTypeV::Scientific, false),207		b'E' => (ConvTypeV::Scientific, true),208		b'f' => (ConvTypeV::Float, false),209		b'F' => (ConvTypeV::Float, true),210		b'g' => (ConvTypeV::Shorter, false),211		b'G' => (ConvTypeV::Shorter, true),212		b'c' => (ConvTypeV::Char, false),213		b's' => (ConvTypeV::String, false),214		b'%' => (ConvTypeV::Percent, false),215		c => return Err(UnrecognizedConversionType(c as char)),216	};217218	Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))219}220221#[derive(Debug)]222pub struct Code<'s> {223	mkey: &'s str,224	cflags: CFlags,225	width: Width,226	precision: Option<Width>,227	convtype: ConvTypeV,228	caps: bool,229}230pub fn parse_code(str: &str) -> ParseResult<Code> {231	if str.is_empty() {232		return Err(TruncatedFormatCode);233	}234	let (mkey, str) = try_parse_mapping_key(str)?;235	let (cflags, str) = try_parse_cflags(str)?;236	let (width, str) = try_parse_field_width(str)?;237	let (precision, str) = try_parse_precision(str)?;238	let (_, str) = try_parse_length_modifier(str)?;239	let (convtype, str) = parse_conversion_type(str)?;240241	Ok((242		Code {243			mkey,244			cflags,245			width,246			precision,247			convtype: convtype.v,248			caps: convtype.caps,249		},250		str,251	))252}253254#[derive(Debug)]255pub enum Element<'s> {256	String(&'s str),257	Code(Code<'s>),258}259pub fn parse_codes(mut str: &str) -> Result<Vec<Element>> {260	let mut bytes = str.as_bytes();261	let mut out = vec![];262	let mut offset = 0;263264	loop {265		while offset != bytes.len() && bytes[offset] != b'%' {266			offset += 1;267		}268		if offset != 0 {269			out.push(Element::String(&str[0..offset]));270		}271		if offset == bytes.len() {272			return Ok(out);273		}274		str = &str[offset + 1..];275		let (code, nstr) = parse_code(str)?;276		str = nstr;277		bytes = str.as_bytes();278		offset = 0;279280		out.push(Element::Code(code))281	}282}283284const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";285286#[inline]287pub fn render_integer(288	out: &mut String,289	iv: i64,290	padding: usize,291	precision: usize,292	blank: bool,293	sign: bool,294	radix: i64,295	prefix: &str,296	caps: bool,297) {298	// Digit char indexes in reverse order, i.e299	// for radix = 16 and n = 12f: [15, 2, 1]300	let digits = if iv == 0 {301		vec![0u8]302	} else {303		let mut v = iv.abs();304		let mut nums = Vec::with_capacity(1);305		while v > 0 {306			nums.push((v % radix) as u8);307			v /= radix;308		}309		nums310	};311	let neg = iv < 0;312	let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });313	let zp2 = zp314		.max(precision)315		.saturating_sub(prefix.len() + digits.len());316317	if neg {318		out.push('-')319	} else if sign {320		out.push('+');321	} else if blank {322		out.push(' ');323	}324325	out.reserve(zp2);326	for _ in 0..zp2 {327		out.push('0');328	}329	out.push_str(prefix);330331	for digit in digits.into_iter().rev() {332		let ch = NUMBERS[digit as usize] as char;333		out.push(if caps { ch.to_ascii_uppercase() } else { ch });334	}335}336337pub fn render_decimal(338	out: &mut String,339	iv: i64,340	padding: usize,341	precision: usize,342	blank: bool,343	sign: bool,344) {345	render_integer(out, iv, padding, precision, blank, sign, 10, "", false)346}347pub fn render_octal(348	out: &mut String,349	iv: i64,350	padding: usize,351	precision: usize,352	alt: bool,353	blank: bool,354	sign: bool,355) {356	render_integer(357		out,358		iv,359		padding,360		precision,361		blank,362		sign,363		8,364		if alt && iv != 0 { "0" } else { "" },365		false,366	)367}368pub fn render_hexadecimal(369	out: &mut String,370	iv: i64,371	padding: usize,372	precision: usize,373	alt: bool,374	blank: bool,375	sign: bool,376	caps: bool,377) {378	render_integer(379		out,380		iv,381		padding,382		precision,383		blank,384		sign,385		16,386		match (alt, caps) {387			(true, true) => "0X",388			(true, false) => "0x",389			(false, _) => "",390		},391		caps,392	)393}394395pub fn render_float(396	out: &mut String,397	n: f64,398	mut padding: usize,399	precision: usize,400	blank: bool,401	sign: bool,402	ensure_pt: bool,403	trailing: bool,404) {405	let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };406	padding = padding.saturating_sub(dot_size + precision);407	render_decimal(out, n.floor() as i64, padding, 0, blank, sign);408	if precision == 0 {409		if ensure_pt {410			out.push('.');411		}412		return;413	}414	let frac = n415		.fract()416		.mul_add(10.0_f64.powf(precision as f64), 0.5)417		.floor();418	if trailing || frac > 0.0 {419		out.push('.');420		let mut frac_str = String::new();421		render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);422		let mut trim = frac_str.len();423		if !trailing {424			for b in frac_str.as_bytes().iter().rev() {425				if *b == b'0' {426					trim -= 1;427				}428			}429		}430		out.push_str(&frac_str[..trim]);431	} else if ensure_pt {432		out.push('.');433	}434}435436pub fn render_float_sci(437	out: &mut String,438	n: f64,439	mut padding: usize,440	precision: usize,441	blank: bool,442	sign: bool,443	ensure_pt: bool,444	trailing: bool,445	caps: bool,446) {447	let exponent = n.log10().floor();448	let mantissa = if exponent as i16 == -324 {449		n * 10.0 / 10.0_f64.powf(exponent + 1.0)450	} else {451		n / 10.0_f64.powf(exponent)452	};453	let mut exponent_str = String::new();454	render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);455456	// +1 for e457	padding = padding.saturating_sub(exponent_str.len() + 1);458459	render_float(460		out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,461	);462	out.push(if caps { 'E' } else { 'e' });463	out.push_str(&exponent_str);464}465466pub fn format_code(467	out: &mut String,468	value: &Val,469	code: &Code,470	width: usize,471	precision: Option<usize>,472) -> Result<()> {473	let clfags = &code.cflags;474	let (fpprec, iprec) = match precision {475		Some(v) => (v, v),476		None => (6, 0),477	};478	let padding = if clfags.zero && !clfags.left {479		width480	} else {481		0482	};483484	// TODO: If left padded, can optimize by writing directly to out485	let mut tmp_out = String::new();486487	match code.convtype {488		ConvTypeV::String => tmp_out.push_str(&value.clone().to_string()?),489		ConvTypeV::Decimal => {490			let value = f64::try_from(value.clone())?;491			render_decimal(492				&mut tmp_out,493				value as i64,494				padding,495				iprec,496				clfags.blank,497				clfags.sign,498			);499		}500		ConvTypeV::Octal => {501			let value = f64::try_from(value.clone())?;502			render_octal(503				&mut tmp_out,504				value as i64,505				padding,506				iprec,507				clfags.alt,508				clfags.blank,509				clfags.sign,510			);511		}512		ConvTypeV::Hexadecimal => {513			let value = f64::try_from(value.clone())?;514			render_hexadecimal(515				&mut tmp_out,516				value as i64,517				padding,518				iprec,519				clfags.alt,520				clfags.blank,521				clfags.sign,522				code.caps,523			);524		}525		ConvTypeV::Scientific => {526			let value = f64::try_from(value.clone())?;527			render_float_sci(528				&mut tmp_out,529				value,530				padding,531				fpprec,532				clfags.blank,533				clfags.sign,534				clfags.alt,535				true,536				code.caps,537			);538		}539		ConvTypeV::Float => {540			let value = f64::try_from(value.clone())?;541			render_float(542				&mut tmp_out,543				value,544				padding,545				fpprec,546				clfags.blank,547				clfags.sign,548				clfags.alt,549				true,550			);551		}552		ConvTypeV::Shorter => {553			let value = f64::try_from(value.clone())?;554			let exponent = value.log10().floor();555			if exponent < -4.0 || exponent >= fpprec as f64 {556				render_float_sci(557					&mut tmp_out,558					value,559					padding,560					fpprec - 1,561					clfags.blank,562					clfags.sign,563					clfags.alt,564					clfags.alt,565					code.caps,566				);567			} else {568				let digits_before_pt = 1.max(exponent as usize + 1);569				render_float(570					&mut tmp_out,571					value,572					padding,573					fpprec - digits_before_pt,574					clfags.blank,575					clfags.sign,576					clfags.alt,577					clfags.alt,578				);579			}580		}581		ConvTypeV::Char => match value.clone() {582			Val::Num(n) => tmp_out583				.push(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?),584			Val::Str(s) => {585				if s.chars().count() != 1 {586					throw!(RuntimeError(587						format!("%c expected 1 char string, got {}", s.chars().count()).into(),588					));589				}590				tmp_out.push_str(&s);591			}592			_ => {593				throw!(TypeMismatch(594					"%c requires number/string",595					vec![ValType::Num, ValType::Str],596					value.value_type(),597				));598			}599		},600		ConvTypeV::Percent => tmp_out.push('%'),601	};602603	let padding = width.saturating_sub(tmp_out.len());604605	if !clfags.left {606		for _ in 0..padding {607			out.push(' ');608		}609	}610	out.push_str(&tmp_out);611	if clfags.left {612		for _ in 0..padding {613			out.push(' ');614		}615	}616617	Ok(())618}619620pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String> {621	let codes = parse_codes(str)?;622	let mut out = String::new();623624	for code in codes {625		match code {626			Element::String(s) => {627				out.push_str(s);628			}629			Element::Code(c) => {630				let width = match c.width {631					Width::Star => {632						if values.is_empty() {633							throw!(NotEnoughValues);634						}635						let value = &values[0];636						values = &values[1..];637						usize::try_from(value.clone())?638					}639					Width::Fixed(n) => n,640				};641				let precision = match c.precision {642					Some(Width::Star) => {643						if values.is_empty() {644							throw!(NotEnoughValues);645						}646						let value = &values[0];647						values = &values[1..];648						Some(usize::try_from(value.clone())?)649					}650					Some(Width::Fixed(n)) => Some(n),651					None => None,652				};653654				// %% should not consume a value655				let value = if c.convtype == ConvTypeV::Percent {656					&Val::Null657				} else {658					if values.is_empty() {659						throw!(NotEnoughValues);660					}661					let value = &values[0];662					values = &values[1..];663					value664				};665666				format_code(&mut out, value, &c, width, precision)?;667			}668		}669	}670671	Ok(out)672}673674pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {675	let codes = parse_codes(str)?;676	let mut out = String::new();677678	for code in codes {679		match code {680			Element::String(s) => {681				out.push_str(s);682			}683			Element::Code(c) => {684				// TODO: Operate on ref685				let f: IStr = c.mkey.into();686				let width = match c.width {687					Width::Star => {688						throw!(CannotUseStarWidthWithObject);689					}690					Width::Fixed(n) => n,691				};692				let precision = match c.precision {693					Some(Width::Star) => {694						throw!(CannotUseStarWidthWithObject);695					}696					Some(Width::Fixed(n)) => Some(n),697					None => None,698				};699700				let value = if c.convtype == ConvTypeV::Percent {701					Val::Null702				} else {703					if f.is_empty() {704						throw!(MappingKeysRequired);705					}706					if let Some(v) = values.get(f.clone())? {707						v708					} else {709						throw!(NoSuchFormatField(f));710					}711				};712713				format_code(&mut out, &value, &c, width, precision)?;714			}715		}716	}717718	Ok(out)719}720721#[cfg(test)]722pub mod test_format {723	use super::*;724725	#[test]726	fn parse() {727		assert_eq!(728			parse_codes(729				"How much error budget is left looking at our %.3f%% availability gurantees?"730			)731			.unwrap()732			.len(),733			4734		);735	}736737	#[test]738	fn octals() {739		assert_eq!(format_arr("%#o", &[Val::Num(8.0)]).unwrap(), "010");740		assert_eq!(format_arr("%#4o", &[Val::Num(8.0)]).unwrap(), " 010");741		assert_eq!(format_arr("%4o", &[Val::Num(8.0)]).unwrap(), "  10");742		assert_eq!(format_arr("%04o", &[Val::Num(8.0)]).unwrap(), "0010");743		assert_eq!(format_arr("%+4o", &[Val::Num(8.0)]).unwrap(), " +10");744		assert_eq!(format_arr("%+04o", &[Val::Num(8.0)]).unwrap(), "+010");745		assert_eq!(format_arr("%-4o", &[Val::Num(8.0)]).unwrap(), "10  ");746		assert_eq!(format_arr("%+-4o", &[Val::Num(8.0)]).unwrap(), "+10 ");747		assert_eq!(format_arr("%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");748	}749750	#[test]751	fn percent_doesnt_consumes_values() {752		assert_eq!(753			format_arr(754				"How much error budget is left looking at our %.3f%% availability gurantees?",755				&[Val::Num(4.0)]756			)757			.unwrap(),758			"How much error budget is left looking at our 4.000% availability gurantees?"759		);760	}761}