difftreelog
fix align std.format output with standard jsonnet changes
in: master
4 files changed
crates/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
crates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth1//! 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}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}crates/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)*) => {$(
crates/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)