git.delta.rocks / jrsonnet / refs/commits / 321e7ee3e21c

difftreelog

source

crates/jrsonnet-evaluator/src/builtin/format.rs15.5 KiBsourcehistory
1//! faster std.format impl2#![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}159160// Only skips161pub 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	// Digit char indexes in reverse order, i.e297	// for radix = 16 and n = 12f: [15, 2, 1]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	// +1 for e455	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	// TODO: If left padded, can optimize by writing directly to out484	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				// %% should not consume a value654				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				// TODO: Operate on ref684				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}