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

difftreelog

fix align std.format output with standard jsonnet changes

mykzwksoYaroslav Bolyukin2026-02-07parent: #c368769.patch.diff
in: master

4 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -225,15 +225,20 @@
 			if v2.get() < 0.0 {
 				bail!("shift by negative exponent")
 			}
-			let exp = ((v2.get() as i64) & 63) as u32;
-			Val::try_num((v1.get() as i64).wrapping_shl(exp) as f64)?
+			let base = v1.truncate_for_bitwise()?;
+			let exp = v2.truncate_for_bitwise()? % 64;
+
+			if exp >= 1 && base >= (1i64 << (63 - exp as u32)) {
+				bail!("left shift would overflow")
+			}
+			Val::try_num(base.wrapping_shl(exp as u32) as f64)?
 		}
 		(Num(v1), Rhs, Num(v2)) => {
 			if v2.get() < 0.0 {
 				bail!("shift by negative exponent")
 			}
 			let exp = ((v2.get() as i64) & 63) as u32;
-			Val::try_num((v1.get() as i64).wrapping_shr(exp) as f64)?
+			Val::try_num(v1.truncate_for_bitwise()?.wrapping_shr(exp) as f64)?
 		}
 
 		// Bigint X Bigint
modifiedcrates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/stdlib/format.rs
1//! faster std.format impl2#![allow(clippy::too_many_arguments)]34use jrsonnet_gcmodule::Trace;5use jrsonnet_interner::IStr;6use jrsonnet_types::ValType;7use thiserror::Error;89use crate::{10	bail,11	error::{format_found, suggest_object_fields, ErrorKind::*},12	typed::Typed,13	Error, ObjValue, Result, Val,14};1516#[derive(Debug, Clone, Error, Trace)]17pub enum FormatError {18	#[error("truncated format code")]19	TruncatedFormatCode,20	#[error("unrecognized conversion type: {0}")]21	UnrecognizedConversionType(char),2223	#[error("not enough values")]24	NotEnoughValues,2526	#[error("cannot use * width with object")]27	CannotUseStarWidthWithObject,28	#[error("mapping keys required")]29	MappingKeysRequired,30	#[error("no such format field: {0}")]31	NoSuchFormatField(IStr),3233	#[error("expected subfield <{0}> to be an object, got {1} instead")]34	SubfieldDidntYieldAnObject(IStr, ValType),35	#[error("subfield not found: <[{full}]{current}>{}", format_found(.found, "subfield"))]36	SubfieldNotFound {37		current: IStr,38		full: IStr,39		found: Box<Vec<IStr>>,40	},41}4243impl From<FormatError> for Error {44	fn from(e: FormatError) -> Self {45		Self::new(Format(e))46	}47}4849use FormatError::*;5051type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;5253pub fn try_parse_mapping_key(str: &str) -> ParseResult<'_, &str> {54	if str.is_empty() {55		return Err(TruncatedFormatCode);56	}57	let bytes = str.as_bytes();58	if bytes[0] == b'(' {59		let mut i = 1;60		while i < bytes.len() {61			if bytes[i] == b')' {62				return Ok((&str[1..i], &str[i + 1..]));63			}64			i += 1;65		}66		Err(TruncatedFormatCode)67	} else {68		Ok(("", str))69	}70}7172#[cfg(test)]73pub mod tests_key {74	use super::*;7576	#[test]77	fn parse_key() {78		assert_eq!(79			try_parse_mapping_key("(hello ) world").unwrap(),80			("hello ", " world")81		);82		assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));83		assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));84		assert_eq!(85			try_parse_mapping_key(" () world").unwrap(),86			("", " () world")87		);88	}8990	#[test]91	#[should_panic = "TruncatedFormatCode"]92	fn parse_key_missing_start() {93		try_parse_mapping_key("").unwrap();94	}9596	#[test]97	#[should_panic = "TruncatedFormatCode"]98	fn parse_key_missing_end() {99		try_parse_mapping_key("(   ").unwrap();100	}101}102103#[allow(clippy::struct_excessive_bools)]104#[derive(Default, Debug)]105pub struct CFlags {106	pub alt: bool,107	pub zero: bool,108	pub left: bool,109	pub blank: bool,110	pub sign: bool,111}112113pub fn try_parse_cflags(str: &str) -> ParseResult<'_, CFlags> {114	if str.is_empty() {115		return Err(TruncatedFormatCode);116	}117	let bytes = str.as_bytes();118	let mut i = 0;119	let mut out = CFlags::default();120	loop {121		if bytes.len() == i {122			return Err(TruncatedFormatCode);123		}124		match bytes[i] {125			b'#' => out.alt = true,126			b'0' => out.zero = true,127			b'-' => out.left = true,128			b' ' => out.blank = true,129			b'+' => out.sign = true,130			_ => break,131		}132		i += 1;133	}134	Ok((out, &str[i..]))135}136137#[derive(Debug, PartialEq, Eq)]138pub enum Width {139	Star,140	Fixed(usize),141}142pub fn try_parse_field_width(str: &str) -> ParseResult<'_, Width> {143	if str.is_empty() {144		return Err(TruncatedFormatCode);145	}146	let bytes = str.as_bytes();147	if bytes[0] == b'*' {148		return Ok((Width::Star, &str[1..]));149	}150	let mut out: usize = 0;151	let mut digits = 0;152	while let Some(digit) = (bytes[digits] as char).to_digit(10) {153		out *= 10;154		out += digit as usize;155		digits += 1;156		if digits == bytes.len() {157			return Err(TruncatedFormatCode);158		}159	}160	Ok((Width::Fixed(out), &str[digits..]))161}162163pub fn try_parse_precision(str: &str) -> ParseResult<'_, Option<Width>> {164	if str.is_empty() {165		return Err(TruncatedFormatCode);166	}167	let bytes = str.as_bytes();168	if bytes[0] == b'.' {169		try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))170	} else {171		Ok((None, str))172	}173}174175// Only skips176pub fn try_parse_length_modifier(str: &str) -> ParseResult<'_, ()> {177	if str.is_empty() {178		return Err(TruncatedFormatCode);179	}180	let bytes = str.as_bytes();181	let mut idx = 0;182	while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {183		idx += 1;184		if bytes.len() == idx {185			return Err(TruncatedFormatCode);186		}187	}188	Ok(((), &str[idx..]))189}190191#[derive(Debug, PartialEq, Eq)]192pub enum ConvTypeV {193	Decimal,194	Octal,195	Hexadecimal,196	Scientific,197	Float,198	Shorter,199	Char,200	String,201	Percent,202}203pub struct ConvType {204	v: ConvTypeV,205	caps: bool,206}207208pub fn parse_conversion_type(str: &str) -> ParseResult<'_, ConvType> {209	if str.is_empty() {210		return Err(TruncatedFormatCode);211	}212213	let code = str.as_bytes()[0];214	let v: (ConvTypeV, bool) = match code {215		b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),216		b'o' => (ConvTypeV::Octal, false),217		b'x' => (ConvTypeV::Hexadecimal, false),218		b'X' => (ConvTypeV::Hexadecimal, true),219		b'e' => (ConvTypeV::Scientific, false),220		b'E' => (ConvTypeV::Scientific, true),221		b'f' => (ConvTypeV::Float, false),222		b'F' => (ConvTypeV::Float, true),223		b'g' => (ConvTypeV::Shorter, false),224		b'G' => (ConvTypeV::Shorter, true),225		b'c' => (ConvTypeV::Char, false),226		b's' => (ConvTypeV::String, false),227		b'%' => (ConvTypeV::Percent, false),228		c => return Err(UnrecognizedConversionType(c as char)),229	};230231	Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))232}233234#[derive(Debug)]235pub struct Code<'s> {236	mkey: &'s str,237	cflags: CFlags,238	width: Width,239	precision: Option<Width>,240	convtype: ConvTypeV,241	caps: bool,242}243pub fn parse_code(str: &str) -> ParseResult<'_, Code<'_>> {244	if str.is_empty() {245		return Err(TruncatedFormatCode);246	}247	let (mkey, str) = try_parse_mapping_key(str)?;248	let (cflags, str) = try_parse_cflags(str)?;249	let (width, str) = try_parse_field_width(str)?;250	let (precision, str) = try_parse_precision(str)?;251	let ((), str) = try_parse_length_modifier(str)?;252	let (convtype, str) = parse_conversion_type(str)?;253254	Ok((255		Code {256			mkey,257			cflags,258			width,259			precision,260			convtype: convtype.v,261			caps: convtype.caps,262		},263		str,264	))265}266267#[derive(Debug)]268pub enum Element<'s> {269	String(&'s str),270	Code(Code<'s>),271}272pub fn parse_codes(mut str: &str) -> Result<Vec<Element<'_>>> {273	let mut bytes = str.as_bytes();274	let mut out = vec![];275	let mut offset = 0;276277	loop {278		while offset != bytes.len() && bytes[offset] != b'%' {279			offset += 1;280		}281		if offset != 0 {282			out.push(Element::String(&str[0..offset]));283		}284		if offset == bytes.len() {285			return Ok(out);286		}287		str = &str[offset + 1..];288		let code;289		(code, str) = parse_code(str)?;290		bytes = str.as_bytes();291		offset = 0;292293		out.push(Element::Code(code));294	}295}296297const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";298299#[inline]300pub fn render_integer(301	out: &mut String,302	iv: f64,303	padding: usize,304	precision: usize,305	blank: bool,306	sign: bool,307	radix: i64,308	prefix: &str,309	caps: bool,310) {311	let iv = iv.floor() as i64;312	// Digit char indexes in reverse order, i.e313	// for radix = 16 and n = 12f: [15, 2, 1]314	let digits = if iv == 0 {315		vec![0u8]316	} else {317		let mut v = iv.abs();318		let mut nums = Vec::with_capacity(1);319		while v != 0 {320			nums.push((v % radix) as u8);321			v /= radix;322		}323		nums324	};325	let neg = iv < 0;326	#[allow(clippy::bool_to_int_with_if)]327	let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });328	let zp2 = zp329		.max(precision)330		.saturating_sub(prefix.len() + digits.len());331332	if neg {333		out.push('-');334	} else if sign {335		out.push('+');336	} else if blank {337		out.push(' ');338	}339340	out.reserve(zp2);341	for _ in 0..zp2 {342		out.push('0');343	}344	out.push_str(prefix);345346	for digit in digits.into_iter().rev() {347		let ch = NUMBERS[digit as usize] as char;348		out.push(if caps { ch.to_ascii_uppercase() } else { ch });349	}350}351352pub fn render_decimal(353	out: &mut String,354	iv: f64,355	padding: usize,356	precision: usize,357	blank: bool,358	sign: bool,359) {360	render_integer(out, iv, padding, precision, blank, sign, 10, "", false);361}362pub fn render_octal(363	out: &mut String,364	iv: f64,365	padding: usize,366	precision: usize,367	alt: bool,368	blank: bool,369	sign: bool,370) {371	render_integer(372		out,373		iv,374		padding,375		precision,376		blank,377		sign,378		8,379		if alt && iv != 0.0 { "0" } else { "" },380		false,381	);382}383384#[allow(clippy::fn_params_excessive_bools)]385pub fn render_hexadecimal(386	out: &mut String,387	iv: f64,388	padding: usize,389	precision: usize,390	alt: bool,391	blank: bool,392	sign: bool,393	caps: bool,394) {395	render_integer(396		out,397		iv,398		padding,399		precision,400		blank,401		sign,402		16,403		match (alt, caps) {404			(true, true) => "0X",405			(true, false) => "0x",406			(false, _) => "",407		},408		caps,409	);410}411412#[allow(clippy::fn_params_excessive_bools)]413pub fn render_float(414	out: &mut String,415	n: f64,416	mut padding: usize,417	precision: usize,418	blank: bool,419	sign: bool,420	ensure_pt: bool,421	trailing: bool,422) {423	#[allow(clippy::bool_to_int_with_if)]424	let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };425	padding = padding.saturating_sub(dot_size + precision);426	render_decimal(out, n.floor(), padding, 0, blank, sign);427	if precision == 0 {428		if ensure_pt {429			out.push('.');430		}431		return;432	}433	let frac = n434		.fract()435		.mul_add(10.0_f64.powf(precision as f64), 0.5)436		.floor();437	if trailing || frac > 0.0 {438		out.push('.');439		let mut frac_str = String::new();440		render_decimal(&mut frac_str, frac, precision, 0, false, false);441		let mut trim = frac_str.len();442		if !trailing {443			for b in frac_str.as_bytes().iter().rev() {444				if *b == b'0' {445					trim -= 1;446				} else {447					break;448				}449			}450		}451		out.push_str(&frac_str[..trim]);452	} else if ensure_pt {453		out.push('.');454	}455}456457#[allow(clippy::fn_params_excessive_bools)]458pub fn render_float_sci(459	out: &mut String,460	n: f64,461	mut padding: usize,462	precision: usize,463	blank: bool,464	sign: bool,465	ensure_pt: bool,466	trailing: bool,467	caps: bool,468) {469	let exponent = n.log10().floor();470	let mantissa = if exponent as i16 == -324 {471		n * 10.0 / 10.0_f64.powf(exponent + 1.0)472	} else {473		n / 10.0_f64.powf(exponent)474	};475	let mut exponent_str = String::new();476	render_decimal(&mut exponent_str, exponent, 3, 0, false, true);477478	// +1 for e479	padding = padding.saturating_sub(exponent_str.len() + 1);480481	render_float(482		out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,483	);484	out.push(if caps { 'E' } else { 'e' });485	out.push_str(&exponent_str);486}487488#[allow(clippy::too_many_lines)]489pub fn format_code(490	out: &mut String,491	value: &Val,492	code: &Code<'_>,493	width: usize,494	precision: Option<usize>,495) -> Result<()> {496	let clfags = &code.cflags;497	let (fpprec, iprec) = precision.map_or((6, 0), |v| (v, v));498	let padding = if clfags.zero && !clfags.left {499		width500	} else {501		0502	};503504	// TODO: If left padded, can optimize by writing directly to out505	let mut tmp_out = String::new();506507	match code.convtype {508		ConvTypeV::String => tmp_out.push_str(&value.clone().to_string()?),509		ConvTypeV::Decimal => {510			let value = f64::from_untyped(value.clone())?;511			render_decimal(512				&mut tmp_out,513				value,514				padding,515				iprec,516				clfags.blank,517				clfags.sign,518			);519		}520		ConvTypeV::Octal => {521			let value = f64::from_untyped(value.clone())?;522			render_octal(523				&mut tmp_out,524				value,525				padding,526				iprec,527				clfags.alt,528				clfags.blank,529				clfags.sign,530			);531		}532		ConvTypeV::Hexadecimal => {533			let value = f64::from_untyped(value.clone())?;534			render_hexadecimal(535				&mut tmp_out,536				value,537				padding,538				iprec,539				clfags.alt,540				clfags.blank,541				clfags.sign,542				code.caps,543			);544		}545		ConvTypeV::Scientific => {546			let value = f64::from_untyped(value.clone())?;547			render_float_sci(548				&mut tmp_out,549				value,550				padding,551				fpprec,552				clfags.blank,553				clfags.sign,554				clfags.alt,555				true,556				code.caps,557			);558		}559		ConvTypeV::Float => {560			let value = f64::from_untyped(value.clone())?;561			render_float(562				&mut tmp_out,563				value,564				padding,565				fpprec,566				clfags.blank,567				clfags.sign,568				clfags.alt,569				true,570			);571		}572		ConvTypeV::Shorter => {573			let value = f64::from_untyped(value.clone())?;574			let exponent = if value == 0.0 {575				0.0576			} else {577				value.abs().log10().floor()578			};579			if exponent < -4.0 || exponent >= fpprec as f64 {580				render_float_sci(581					&mut tmp_out,582					value,583					padding,584					fpprec - 1,585					clfags.blank,586					clfags.sign,587					clfags.alt,588					clfags.alt,589					code.caps,590				);591			} else {592				let digits_before_pt = 1.max(exponent as usize + 1);593				render_float(594					&mut tmp_out,595					value,596					padding,597					fpprec - digits_before_pt,598					clfags.blank,599					clfags.sign,600					clfags.alt,601					clfags.alt,602				);603			}604		}605		ConvTypeV::Char => match value.clone() {606			Val::Num(n) => {607				let n = n.get();608				tmp_out.push(609					std::char::from_u32(n as u32)610						.ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,611				);612			}613			Val::Str(s) => {614				let s = s.into_flat();615				if s.chars().count() != 1 {616					bail!("%c expected 1 char string, got {}", s.chars().count());617				}618				tmp_out.push_str(&s);619			}620			_ => {621				bail!(TypeMismatch(622					"%c requires number/string",623					vec![ValType::Num, ValType::Str],624					value.value_type(),625				));626			}627		},628		ConvTypeV::Percent => tmp_out.push('%'),629	};630631	let padding = width.saturating_sub(tmp_out.len());632633	if !clfags.left {634		for _ in 0..padding {635			out.push(' ');636		}637	}638	out.push_str(&tmp_out);639	if clfags.left {640		for _ in 0..padding {641			out.push(' ');642		}643	}644645	Ok(())646}647648pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String> {649	let codes = parse_codes(str)?;650	let mut out = String::new();651	let value_count = values.len();652653	for code in codes {654		match code {655			Element::String(s) => {656				out.push_str(s);657			}658			Element::Code(c) => {659				let width = match c.width {660					Width::Star => {661						if values.is_empty() {662							bail!(NotEnoughValues);663						}664						let value = &values[0];665						values = &values[1..];666						usize::from_untyped(value.clone())?667					}668					Width::Fixed(n) => n,669				};670				let precision = match c.precision {671					Some(Width::Star) => {672						if values.is_empty() {673							bail!(NotEnoughValues);674						}675						let value = &values[0];676						values = &values[1..];677						Some(usize::from_untyped(value.clone())?)678					}679					Some(Width::Fixed(n)) => Some(n),680					None => None,681				};682683				// %% should not consume a value684				let value = if c.convtype == ConvTypeV::Percent {685					&Val::Null686				} else {687					if values.is_empty() {688						bail!(NotEnoughValues);689					}690					let value = &values[0];691					values = &values[1..];692					value693				};694695				format_code(&mut out, value, &c, width, precision)?;696			}697		}698	}699700	if !values.is_empty() {701		bail!(702			"too many values to format, expected {value_count}, got {}",703			value_count + values.len()704		)705	}706707	Ok(out)708}709710fn get_dotted_field(obj: ObjValue, field: &str) -> Result<Val> {711	let mut current = Val::Obj(obj);712	let mut name_offset = 0;713	for component in field.split('.') {714		let end_offset = name_offset + component.len();715		current = if let Val::Obj(obj) = current {716			if let Some(value) = obj.get(component.into())? {717				value718			} else {719				let current = &field[name_offset..end_offset];720				let full = &field[..name_offset];721				let found = Box::new(suggest_object_fields(&obj, current.into()));722				bail!(SubfieldNotFound {723					current: current.into(),724					full: full.into(),725					found,726				})727			}728		} else {729			// No underflow may happen, initially we always start with an object730			let subfield = &field[..name_offset - 1];731			bail!(SubfieldDidntYieldAnObject(732				subfield.into(),733				current.value_type()734			));735		};736		name_offset = end_offset + 1;737	}738	Ok(current)739}740741pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {742	let codes = parse_codes(str)?;743	let mut out = String::new();744745	for code in codes {746		match code {747			Element::String(s) => {748				out.push_str(s);749			}750			Element::Code(c) => {751				// TODO: Operate on ref752				let f: IStr = c.mkey.into();753				let width = match c.width {754					Width::Star => {755						bail!(CannotUseStarWidthWithObject);756					}757					Width::Fixed(n) => n,758				};759				let precision = match c.precision {760					Some(Width::Star) => {761						bail!(CannotUseStarWidthWithObject);762					}763					Some(Width::Fixed(n)) => Some(n),764					None => None,765				};766767				let value = if c.convtype == ConvTypeV::Percent {768					Val::Null769				} else {770					if f.is_empty() {771						bail!(MappingKeysRequired);772					}773					if let Some(v) = values.get(f.clone())? {774						v775					} else {776						get_dotted_field(values.clone(), &f)?777					}778				};779780				format_code(&mut out, &value, &c, width, precision)?;781			}782		}783	}784785	Ok(out)786}787788#[cfg(test)]789pub mod test_format {790	use super::*;791	use crate::val::NumValue;792793	#[test]794	fn parse() {795		assert_eq!(796			parse_codes(797				"How much error budget is left looking at our %.3f%% availability gurantees?"798			)799			.unwrap()800			.len(),801			4802		);803	}804805	fn num(v: f64) -> Val {806		Val::Num(NumValue::new(v).expect("finite"))807	}808809	#[test]810	fn octals() {811		assert_eq!(format_arr("%#o", &[num(8.0)]).unwrap(), "010");812		assert_eq!(format_arr("%#4o", &[num(8.0)]).unwrap(), " 010");813		assert_eq!(format_arr("%4o", &[num(8.0)]).unwrap(), "  10");814		assert_eq!(format_arr("%04o", &[num(8.0)]).unwrap(), "0010");815		assert_eq!(format_arr("%+4o", &[num(8.0)]).unwrap(), " +10");816		assert_eq!(format_arr("%+04o", &[num(8.0)]).unwrap(), "+010");817		assert_eq!(format_arr("%-4o", &[num(8.0)]).unwrap(), "10  ");818		assert_eq!(format_arr("%+-4o", &[num(8.0)]).unwrap(), "+10 ");819		assert_eq!(format_arr("%+-04o", &[num(8.0)]).unwrap(), "+10 ");820	}821822	#[test]823	fn percent_doesnt_consumes_values() {824		assert_eq!(825			format_arr(826				"How much error budget is left looking at our %.3f%% availability gurantees?",827				&[num(4.0)]828			)829			.unwrap(),830			"How much error budget is left looking at our 4.000% availability gurantees?"831		);832	}833}
after · crates/jrsonnet-evaluator/src/stdlib/format.rs
1//! faster std.format impl2#![allow(clippy::too_many_arguments)]34use jrsonnet_gcmodule::Trace;5use jrsonnet_interner::IStr;6use jrsonnet_types::ValType;7use thiserror::Error;89use crate::{10	bail,11	error::{format_found, suggest_object_fields, ErrorKind::*},12	typed::Typed,13	Error, ObjValue, Result, Val,14};1516#[derive(Debug, Clone, Error, Trace)]17pub enum FormatError {18	#[error("truncated format code")]19	TruncatedFormatCode,20	#[error("unrecognized conversion type: {0}")]21	UnrecognizedConversionType(char),2223	#[error("not enough values")]24	NotEnoughValues,2526	#[error("cannot use * width with object")]27	CannotUseStarWidthWithObject,28	#[error("mapping keys required")]29	MappingKeysRequired,30	#[error("no such format field: {0}")]31	NoSuchFormatField(IStr),3233	#[error("expected subfield <{0}> to be an object, got {1} instead")]34	SubfieldDidntYieldAnObject(IStr, ValType),35	#[error("subfield not found: <[{full}]{current}>{}", format_found(.found, "subfield"))]36	SubfieldNotFound {37		current: IStr,38		full: IStr,39		found: Box<Vec<IStr>>,40	},41}4243impl From<FormatError> for Error {44	fn from(e: FormatError) -> Self {45		Self::new(Format(e))46	}47}4849use FormatError::*;5051type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;5253pub fn try_parse_mapping_key(str: &str) -> ParseResult<'_, &str> {54	if str.is_empty() {55		return Err(TruncatedFormatCode);56	}57	let bytes = str.as_bytes();58	if bytes[0] == b'(' {59		let mut i = 1;60		while i < bytes.len() {61			if bytes[i] == b')' {62				return Ok((&str[1..i], &str[i + 1..]));63			}64			i += 1;65		}66		Err(TruncatedFormatCode)67	} else {68		Ok(("", str))69	}70}7172#[cfg(test)]73pub mod tests_key {74	use super::*;7576	#[test]77	fn parse_key() {78		assert_eq!(79			try_parse_mapping_key("(hello ) world").unwrap(),80			("hello ", " world")81		);82		assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));83		assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));84		assert_eq!(85			try_parse_mapping_key(" () world").unwrap(),86			("", " () world")87		);88	}8990	#[test]91	#[should_panic = "TruncatedFormatCode"]92	fn parse_key_missing_start() {93		try_parse_mapping_key("").unwrap();94	}9596	#[test]97	#[should_panic = "TruncatedFormatCode"]98	fn parse_key_missing_end() {99		try_parse_mapping_key("(   ").unwrap();100	}101}102103#[allow(clippy::struct_excessive_bools)]104#[derive(Default, Debug)]105pub struct CFlags {106	pub alt: bool,107	pub zero: bool,108	pub left: bool,109	pub blank: bool,110	pub sign: bool,111}112113pub fn try_parse_cflags(str: &str) -> ParseResult<'_, CFlags> {114	if str.is_empty() {115		return Err(TruncatedFormatCode);116	}117	let bytes = str.as_bytes();118	let mut i = 0;119	let mut out = CFlags::default();120	loop {121		if bytes.len() == i {122			return Err(TruncatedFormatCode);123		}124		match bytes[i] {125			b'#' => out.alt = true,126			b'0' => out.zero = true,127			b'-' => out.left = true,128			b' ' => out.blank = true,129			b'+' => out.sign = true,130			_ => break,131		}132		i += 1;133	}134	Ok((out, &str[i..]))135}136137#[derive(Debug, PartialEq, Eq)]138pub enum Width {139	Star,140	Fixed(u16),141}142pub fn try_parse_field_width(str: &str) -> ParseResult<'_, Width> {143	if str.is_empty() {144		return Err(TruncatedFormatCode);145	}146	let bytes = str.as_bytes();147	if bytes[0] == b'*' {148		return Ok((Width::Star, &str[1..]));149	}150	let mut out: u16 = 0;151	let mut digits = 0;152	while let Some(digit) = (bytes[digits] as char).to_digit(10) {153		out *= 10;154		out += digit as u16;155		digits += 1;156		if digits == bytes.len() {157			return Err(TruncatedFormatCode);158		}159	}160	Ok((Width::Fixed(out), &str[digits..]))161}162163pub fn try_parse_precision(str: &str) -> ParseResult<'_, Option<Width>> {164	if str.is_empty() {165		return Err(TruncatedFormatCode);166	}167	let bytes = str.as_bytes();168	if bytes[0] == b'.' {169		try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))170	} else {171		Ok((None, str))172	}173}174175// Only skips176pub fn try_parse_length_modifier(str: &str) -> ParseResult<'_, ()> {177	if str.is_empty() {178		return Err(TruncatedFormatCode);179	}180	let bytes = str.as_bytes();181	let mut idx = 0;182	while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {183		idx += 1;184		if bytes.len() == idx {185			return Err(TruncatedFormatCode);186		}187	}188	Ok(((), &str[idx..]))189}190191#[derive(Debug, PartialEq, Eq)]192pub enum ConvTypeV {193	Decimal,194	Octal,195	Hexadecimal,196	Scientific,197	Float,198	Shorter,199	Char,200	String,201	Percent,202}203pub struct ConvType {204	v: ConvTypeV,205	caps: bool,206}207208pub fn parse_conversion_type(str: &str) -> ParseResult<'_, ConvType> {209	if str.is_empty() {210		return Err(TruncatedFormatCode);211	}212213	let code = str.as_bytes()[0];214	let v: (ConvTypeV, bool) = match code {215		b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),216		b'o' => (ConvTypeV::Octal, false),217		b'x' => (ConvTypeV::Hexadecimal, false),218		b'X' => (ConvTypeV::Hexadecimal, true),219		b'e' => (ConvTypeV::Scientific, false),220		b'E' => (ConvTypeV::Scientific, true),221		b'f' => (ConvTypeV::Float, false),222		b'F' => (ConvTypeV::Float, true),223		b'g' => (ConvTypeV::Shorter, false),224		b'G' => (ConvTypeV::Shorter, true),225		b'c' => (ConvTypeV::Char, false),226		b's' => (ConvTypeV::String, false),227		b'%' => (ConvTypeV::Percent, false),228		c => return Err(UnrecognizedConversionType(c as char)),229	};230231	Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))232}233234#[derive(Debug)]235pub struct Code<'s> {236	mkey: &'s str,237	cflags: CFlags,238	width: Width,239	precision: Option<Width>,240	convtype: ConvTypeV,241	caps: bool,242}243pub fn parse_code(str: &str) -> ParseResult<'_, Code<'_>> {244	if str.is_empty() {245		return Err(TruncatedFormatCode);246	}247	let (mkey, str) = try_parse_mapping_key(str)?;248	let (cflags, str) = try_parse_cflags(str)?;249	let (width, str) = try_parse_field_width(str)?;250	let (precision, str) = try_parse_precision(str)?;251	let ((), str) = try_parse_length_modifier(str)?;252	let (convtype, str) = parse_conversion_type(str)?;253254	Ok((255		Code {256			mkey,257			cflags,258			width,259			precision,260			convtype: convtype.v,261			caps: convtype.caps,262		},263		str,264	))265}266267#[derive(Debug)]268pub enum Element<'s> {269	String(&'s str),270	Code(Code<'s>),271}272pub fn parse_codes(mut str: &str) -> Result<Vec<Element<'_>>> {273	let mut bytes = str.as_bytes();274	let mut out = vec![];275	let mut offset = 0;276277	loop {278		while offset != bytes.len() && bytes[offset] != b'%' {279			offset += 1;280		}281		if offset != 0 {282			out.push(Element::String(&str[0..offset]));283		}284		if offset == bytes.len() {285			return Ok(out);286		}287		str = &str[offset + 1..];288		let code;289		(code, str) = parse_code(str)?;290		bytes = str.as_bytes();291		offset = 0;292293		out.push(Element::Code(code));294	}295}296297const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";298299#[inline]300pub fn render_integer(301	out: &mut String,302	neg: bool,303	iv: f64,304	padding: u16,305	precision: u16,306	blank: bool,307	sign: bool,308	radix: i64,309	zero_prefix: &str,310	prefix_in_padding: bool,311	caps: bool,312) {313	debug_assert!(iv >= 0.0, "render_integer receives sign using arg");314	let iv = iv.floor() as i64;315	// Digit char indexes in reverse order, i.e316	// for radix = 16 and n = 12f: [15, 2, 1]317	let digits = if iv == 0 {318		vec![0u8]319	} else {320		let mut v = iv.abs();321		let mut nums = Vec::with_capacity(1);322		while v != 0 {323			nums.push((v % radix) as u8);324			v /= radix;325		}326		nums327	};328	#[allow(clippy::bool_to_int_with_if)]329	let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });330331	let pref_len = zero_prefix.len() as u16;332	let zp2 = zp333		.saturating_sub(if !prefix_in_padding { pref_len } else { 0 })334		.max(precision)335		.saturating_sub(if prefix_in_padding { pref_len } else { 0 } + digits.len() as u16);336337	if neg {338		out.push('-');339	} else if sign {340		out.push('+');341	} else if blank {342		out.push(' ');343	}344345	out.reserve(zp2 as usize);346	if iv != 0 {347		out.push_str(zero_prefix);348	}349	for _ in 0..zp2 {350		out.push('0');351	}352353	for digit in digits.into_iter().rev() {354		let ch = NUMBERS[digit as usize] as char;355		out.push(if caps { ch.to_ascii_uppercase() } else { ch });356	}357}358359pub fn render_decimal(360	out: &mut String,361	neg: bool,362	iv: f64,363	padding: u16,364	precision: u16,365	blank: bool,366	sign: bool,367) {368	render_integer(369		out, neg, iv, padding, precision, blank, sign, 10, "", false, false,370	);371}372pub fn render_octal(373	out: &mut String,374	neg: bool,375	iv: f64,376	padding: u16,377	precision: u16,378	alt: bool,379	blank: bool,380	sign: bool,381) {382	render_integer(383		out,384		neg,385		iv,386		padding,387		precision,388		blank,389		sign,390		8,391		if alt && iv != 0.0 { "0" } else { "" },392		true,393		false,394	);395}396397#[allow(clippy::fn_params_excessive_bools)]398pub fn render_hexadecimal(399	out: &mut String,400	iv: f64,401	padding: u16,402	precision: u16,403	alt: bool,404	blank: bool,405	sign: bool,406	caps: bool,407) {408	render_integer(409		out,410		iv < 0.0,411		iv.abs(),412		padding,413		precision,414		blank,415		sign,416		16,417		match (alt, caps) {418			(true, true) => "0X",419			(true, false) => "0x",420			(false, _) => "",421		},422		false,423		caps,424	);425}426427#[allow(clippy::fn_params_excessive_bools)]428pub fn render_float(429	out: &mut String,430	n: f64,431	mut padding: u16,432	precision: u16,433	blank: bool,434	sign: bool,435	ensure_pt: bool,436	trailing: bool,437) {438	// Represent the rounded number as an integer * 1/10**prec.439	// Note that it can also be equal to 10**prec and we'll need to carry440	// over to the wholes.  We operate on the absolute numbers, so that we441	// don't have trouble with the rounding direction.442	let denominator = 10.0f64.powi(precision as i32);443	let numerator = n.abs() * denominator + 0.5;444	let whole = (numerator / denominator).floor();445	let frac = numerator.floor() % denominator;446447	#[allow(clippy::bool_to_int_with_if)]448	let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };449	padding = padding.saturating_sub(dot_size + precision);450	render_decimal(out, n < 0.0, whole, padding, 0, blank, sign);451	if precision == 0 {452		if ensure_pt {453			out.push('.');454		}455		return;456	}457	if trailing || frac > 0.0 {458		out.push('.');459		let mut frac_str = String::new();460		render_decimal(&mut frac_str, false, frac, precision, 0, false, false);461		let mut trim = frac_str.len();462		if !trailing {463			for b in frac_str.as_bytes().iter().rev() {464				if *b == b'0' {465					trim -= 1;466				} else {467					break;468				}469			}470		}471		out.push_str(&frac_str[..trim]);472	} else if ensure_pt {473		out.push('.');474	}475}476477#[allow(clippy::fn_params_excessive_bools)]478pub fn render_float_sci(479	out: &mut String,480	n: f64,481	mut padding: u16,482	precision: u16,483	blank: bool,484	sign: bool,485	ensure_pt: bool,486	trailing: bool,487	caps: bool,488) {489	let exponent = if n == 0.0 {490		0.0491	} else {492		n.abs().log10().floor()493	};494495	let mantissa = if exponent as i16 == -324 {496		n * 10.0 / 10.0_f64.powf(exponent + 1.0)497	} else {498		n / 10.0_f64.powf(exponent)499	};500	let mut exponent_str = String::new();501	render_decimal(502		&mut exponent_str,503		exponent < 0.0,504		exponent.abs(),505		3,506		0,507		false,508		true,509	);510511	// +1 for e512	padding = padding.saturating_sub(exponent_str.len() as u16 + 1);513514	render_float(515		out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,516	);517	out.push(if caps { 'E' } else { 'e' });518	out.push_str(&exponent_str);519}520521#[allow(clippy::too_many_lines)]522pub fn format_code(523	out: &mut String,524	value: &Val,525	code: &Code<'_>,526	width: u16,527	precision: Option<u16>,528) -> Result<()> {529	let clfags = &code.cflags;530	let (fpprec, iprec) = precision.map_or((6, 0), |v| (v, v));531	let padding = if clfags.zero && !clfags.left {532		width533	} else {534		0535	};536537	// TODO: If left padded, can optimize by writing directly to out538	let mut tmp_out = String::new();539540	match code.convtype {541		ConvTypeV::String => tmp_out.push_str(&value.clone().to_string()?),542		ConvTypeV::Decimal => {543			let value = f64::from_untyped(value.clone())?;544			render_decimal(545				&mut tmp_out,546				value <= -1.0,547				value.abs(),548				padding,549				iprec,550				clfags.blank,551				clfags.sign,552			);553		}554		ConvTypeV::Octal => {555			let value = f64::from_untyped(value.clone())?;556			render_octal(557				&mut tmp_out,558				value <= -1.0,559				value.abs(),560				padding,561				iprec,562				clfags.alt,563				clfags.blank,564				clfags.sign,565			);566		}567		ConvTypeV::Hexadecimal => {568			let value = f64::from_untyped(value.clone())?;569			render_hexadecimal(570				&mut tmp_out,571				value,572				padding,573				iprec,574				clfags.alt,575				clfags.blank,576				clfags.sign,577				code.caps,578			);579		}580		ConvTypeV::Scientific => {581			let value = f64::from_untyped(value.clone())?;582			render_float_sci(583				&mut tmp_out,584				value,585				padding,586				fpprec,587				clfags.blank,588				clfags.sign,589				clfags.alt,590				true,591				code.caps,592			);593		}594		ConvTypeV::Float => {595			let value = f64::from_untyped(value.clone())?;596			render_float(597				&mut tmp_out,598				value,599				padding,600				fpprec,601				clfags.blank,602				clfags.sign,603				clfags.alt,604				true,605			);606		}607		ConvTypeV::Shorter => {608			let value = f64::from_untyped(value.clone())?;609			let exponent = if value == 0.0 {610				0.0611			} else {612				value.abs().log10().floor()613			};614			if exponent < -4.0 || exponent >= fpprec as f64 {615				render_float_sci(616					&mut tmp_out,617					value,618					padding,619					fpprec - 1,620					clfags.blank,621					clfags.sign,622					clfags.alt,623					clfags.alt,624					code.caps,625				);626			} else {627				let digits_before_pt = 1.max(exponent as u16 + 1);628				render_float(629					&mut tmp_out,630					value,631					padding,632					fpprec - digits_before_pt,633					clfags.blank,634					clfags.sign,635					clfags.alt,636					clfags.alt,637				);638			}639		}640		ConvTypeV::Char => match value.clone() {641			Val::Num(n) => {642				let n = n.get();643				tmp_out.push(644					std::char::from_u32(n as u32)645						.ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,646				);647			}648			Val::Str(s) => {649				let s = s.into_flat();650				if s.chars().count() != 1 {651					bail!("%c expected 1 char string, got {}", s.chars().count());652				}653				tmp_out.push_str(&s);654			}655			_ => {656				bail!(TypeMismatch(657					"%c requires number/string",658					vec![ValType::Num, ValType::Str],659					value.value_type(),660				));661			}662		},663		ConvTypeV::Percent => tmp_out.push('%'),664	};665666	let padding = width.saturating_sub(tmp_out.len() as u16);667668	if !clfags.left {669		for _ in 0..padding {670			out.push(' ');671		}672	}673	out.push_str(&tmp_out);674	if clfags.left {675		for _ in 0..padding {676			out.push(' ');677		}678	}679680	Ok(())681}682683pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String> {684	let codes = parse_codes(str)?;685	let mut out = String::new();686	let value_count = values.len();687688	for code in codes {689		match code {690			Element::String(s) => {691				out.push_str(s);692			}693			Element::Code(c) => {694				let width = match c.width {695					Width::Star => {696						if values.is_empty() {697							bail!(NotEnoughValues);698						}699						let value = &values[0];700						values = &values[1..];701						u16::from_untyped(value.clone())?702					}703					Width::Fixed(n) => n,704				};705				let precision = match c.precision {706					Some(Width::Star) => {707						if values.is_empty() {708							bail!(NotEnoughValues);709						}710						let value = &values[0];711						values = &values[1..];712						Some(u16::from_untyped(value.clone())?)713					}714					Some(Width::Fixed(n)) => Some(n),715					None => None,716				};717718				// %% should not consume a value719				let value = if c.convtype == ConvTypeV::Percent {720					&Val::Null721				} else {722					if values.is_empty() {723						bail!(NotEnoughValues);724					}725					let value = &values[0];726					values = &values[1..];727					value728				};729730				format_code(&mut out, value, &c, width, precision)?;731			}732		}733	}734735	if !values.is_empty() {736		bail!(737			"too many values to format, expected {value_count}, got {}",738			value_count + values.len()739		)740	}741742	Ok(out)743}744745fn get_dotted_field(obj: ObjValue, field: &str) -> Result<Val> {746	let mut current = Val::Obj(obj);747	let mut name_offset = 0;748	for component in field.split('.') {749		let end_offset = name_offset + component.len();750		current = if let Val::Obj(obj) = current {751			if let Some(value) = obj.get(component.into())? {752				value753			} else {754				let current = &field[name_offset..end_offset];755				let full = &field[..name_offset];756				let found = Box::new(suggest_object_fields(&obj, current.into()));757				bail!(SubfieldNotFound {758					current: current.into(),759					full: full.into(),760					found,761				})762			}763		} else {764			// No underflow may happen, initially we always start with an object765			let subfield = &field[..name_offset - 1];766			bail!(SubfieldDidntYieldAnObject(767				subfield.into(),768				current.value_type()769			));770		};771		name_offset = end_offset + 1;772	}773	Ok(current)774}775776pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {777	let codes = parse_codes(str)?;778	let mut out = String::new();779780	for code in codes {781		match code {782			Element::String(s) => {783				out.push_str(s);784			}785			Element::Code(c) => {786				// TODO: Operate on ref787				let f: IStr = c.mkey.into();788				let width = match c.width {789					Width::Star => {790						bail!(CannotUseStarWidthWithObject);791					}792					Width::Fixed(n) => n,793				};794				let precision = match c.precision {795					Some(Width::Star) => {796						bail!(CannotUseStarWidthWithObject);797					}798					Some(Width::Fixed(n)) => Some(n),799					None => None,800				};801802				let value = if c.convtype == ConvTypeV::Percent {803					Val::Null804				} else {805					if f.is_empty() {806						bail!(MappingKeysRequired);807					}808					if let Some(v) = values.get(f.clone())? {809						v810					} else {811						get_dotted_field(values.clone(), &f)?812					}813				};814815				format_code(&mut out, &value, &c, width, precision)?;816			}817		}818	}819820	Ok(out)821}822823#[cfg(test)]824pub mod test_format {825	use super::*;826	use crate::val::NumValue;827828	#[test]829	fn parse() {830		assert_eq!(831			parse_codes(832				"How much error budget is left looking at our %.3f%% availability gurantees?"833			)834			.unwrap()835			.len(),836			4837		);838	}839840	fn num(v: f64) -> Val {841		Val::Num(NumValue::new(v).expect("finite"))842	}843844	#[test]845	fn octals() {846		assert_eq!(format_arr("%#o", &[num(8.0)]).unwrap(), "010");847		assert_eq!(format_arr("%#4o", &[num(8.0)]).unwrap(), " 010");848		assert_eq!(format_arr("%4o", &[num(8.0)]).unwrap(), "  10");849		assert_eq!(format_arr("%04o", &[num(8.0)]).unwrap(), "0010");850		assert_eq!(format_arr("%+4o", &[num(8.0)]).unwrap(), " +10");851		assert_eq!(format_arr("%+04o", &[num(8.0)]).unwrap(), "+010");852		assert_eq!(format_arr("%-4o", &[num(8.0)]).unwrap(), "10  ");853		assert_eq!(format_arr("%+-4o", &[num(8.0)]).unwrap(), "+10 ");854		assert_eq!(format_arr("%+-04o", &[num(8.0)]).unwrap(), "+10 ");855	}856857	#[test]858	fn percent_doesnt_consumes_values() {859		assert_eq!(860			format_arr(861				"How much error budget is left looking at our %.3f%% availability gurantees?",862				&[num(4.0)]863			)864			.unwrap(),865			"How much error budget is left looking at our 4.000% availability gurantees?"866		);867	}868}
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -120,8 +120,8 @@
 	}
 }
 
-pub const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS + 1)) - 1) as f64;
-pub const MIN_SAFE_INTEGER: f64 = -MAX_SAFE_INTEGER;
+pub const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS)) - 1) as f64;
+pub const MIN_SAFE_INTEGER: f64 = (-((1i64 << (f64::MANTISSA_DIGITS)) - 1)) as f64;
 
 macro_rules! impl_int {
 	($($ty:ty)*) => {$(
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -22,7 +22,7 @@
 	function::FuncVal,
 	gc::WithCapacityExt as _,
 	manifest::{ManifestFormat, ToStringFormat},
-	typed::BoundedUsize,
+	typed::{BoundedUsize, MAX_SAFE_INTEGER, MIN_SAFE_INTEGER},
 	ObjValue, Result, Unbound, WeakObjValue,
 };
 
@@ -418,6 +418,12 @@
 	pub const fn get(&self) -> f64 {
 		self.0
 	}
+	pub(crate) fn truncate_for_bitwise(&self) -> Result<i64> {
+		if self.0 < MIN_SAFE_INTEGER || self.0 > dbg!(MAX_SAFE_INTEGER) {
+			bail!("numberic value outside of safe integer range for bitwise operation");
+		}
+		Ok(self.0 as i64)
+	}
 }
 impl PartialEq for NumValue {
 	fn eq(&self, other: &Self) -> bool {
@@ -490,7 +496,6 @@
 			type Error = ConvertNumValueError;
 			#[inline]
 			fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {
-				use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER};
 				let value = value as f64;
 				if value < MIN_SAFE_INTEGER {
 					return Err(ConvertNumValueError::Underflow)