difftreelog
feat add intristic calls to stack
in: master
2 files changed
crates/jrsonnet-evaluator/src/builtin/format.rsdiffbeforeafterboth1//! faster std.format impl2#![allow(clippy::too_many_arguments)]34use crate::{error::Error::*, throw, to_string, LocError, ObjValue, Result, Val, ValType};56#[derive(Debug, Clone)]7pub enum FormatError {8 TruncatedFormatCode,9 UnrecognizedConversionType(char),1011 NotEnoughValues,1213 CannotUseStarWidthWithObject,14 MappingKeysRequired,15 NoSuchFormatField(Rc<str>),16}1718impl From<FormatError> for LocError {19 fn from(e: FormatError) -> Self {20 Self::new(Format(e))21 }22}2324use std::rc::Rc;25use FormatError::*;2627type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;2829pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> {30 if str.is_empty() {31 return Err(TruncatedFormatCode);32 }33 let bytes = str.as_bytes();34 if bytes[0] == b'(' {35 let mut i = 1;36 while i < bytes.len() {37 if bytes[i] == b')' {38 return Ok((&str[1..i as usize], &str[i as usize + 1..]));39 }40 i += 1;41 }42 Err(TruncatedFormatCode)43 } else {44 Ok(("", str))45 }46}4748#[cfg(test)]49pub mod tests_key {50 use super::*;5152 #[test]53 fn parse_key() {54 assert_eq!(55 try_parse_mapping_key("(hello ) world").unwrap(),56 ("hello ", " world")57 );58 assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));59 assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));60 assert_eq!(61 try_parse_mapping_key(" () world").unwrap(),62 ("", " () world")63 );64 }6566 #[test]67 #[should_panic]68 fn parse_key_missing_start() {69 try_parse_mapping_key("").unwrap();70 }7172 #[test]73 #[should_panic]74 fn parse_key_missing_end() {75 try_parse_mapping_key("( ").unwrap();76 }77}7879#[derive(Default, Debug)]80pub struct CFlags {81 pub alt: bool,82 pub zero: bool,83 pub left: bool,84 pub blank: bool,85 pub sign: bool,86}8788pub fn try_parse_cflags(str: &str) -> ParseResult<CFlags> {89 if str.is_empty() {90 return Err(TruncatedFormatCode);91 }92 let bytes = str.as_bytes();93 let mut i = 0;94 let mut out = CFlags::default();95 loop {96 if bytes.len() == i {97 return Err(TruncatedFormatCode);98 }99 match bytes[i] {100 b'#' => out.alt = true,101 b'0' => out.zero = true,102 b'-' => out.left = true,103 b' ' => out.blank = true,104 b'+' => out.sign = true,105 _ => break,106 }107 i += 1;108 }109 Ok((out, &str[i..]))110}111112#[derive(Debug, PartialEq)]113pub enum Width {114 Star,115 Fixed(usize),116}117pub fn try_parse_field_width(str: &str) -> ParseResult<Width> {118 if str.is_empty() {119 return Err(TruncatedFormatCode);120 }121 let bytes = str.as_bytes();122 if bytes[0] == b'*' {123 return Ok((Width::Star, &str[1..]));124 }125 let mut out: usize = 0;126 let mut digits = 0;127 while let Some(digit) = (bytes[digits] as char).to_digit(10) {128 out *= 10;129 out += digit as usize;130 digits += 1;131 if digits == bytes.len() {132 return Err(TruncatedFormatCode);133 }134 }135 Ok((Width::Fixed(out), &str[digits..]))136}137138pub fn try_parse_precision(str: &str) -> ParseResult<Option<Width>> {139 if str.is_empty() {140 return Err(TruncatedFormatCode);141 }142 let bytes = str.as_bytes();143 if bytes[0] == b'.' {144 try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))145 } else {146 Ok((None, str))147 }148}149150// Only skips151pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> {152 if str.is_empty() {153 return Err(TruncatedFormatCode);154 }155 let bytes = str.as_bytes();156 let mut idx = 0;157 while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {158 idx += 1;159 if bytes.len() == idx {160 return Err(TruncatedFormatCode);161 }162 }163 Ok(((), &str[idx..]))164}165166#[derive(Debug, PartialEq)]167pub enum ConvTypeV {168 Decimal,169 Octal,170 Hexadecimal,171 Scientific,172 Float,173 Shorter,174 Char,175 String,176 Percent,177}178pub struct ConvType {179 v: ConvTypeV,180 caps: bool,181}182183pub fn parse_conversion_type(str: &str) -> ParseResult<ConvType> {184 if str.is_empty() {185 return Err(TruncatedFormatCode);186 }187188 let code = str.as_bytes()[0];189 let v: (ConvTypeV, bool) = match code {190 b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),191 b'o' => (ConvTypeV::Octal, false),192 b'x' => (ConvTypeV::Hexadecimal, false),193 b'X' => (ConvTypeV::Hexadecimal, true),194 b'e' => (ConvTypeV::Scientific, false),195 b'E' => (ConvTypeV::Scientific, true),196 b'f' => (ConvTypeV::Float, false),197 b'F' => (ConvTypeV::Float, true),198 b'g' => (ConvTypeV::Shorter, false),199 b'G' => (ConvTypeV::Shorter, true),200 b'c' => (ConvTypeV::Char, false),201 b's' => (ConvTypeV::String, false),202 b'%' => (ConvTypeV::Percent, false),203 c => return Err(UnrecognizedConversionType(c as char)),204 };205206 Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))207}208209#[derive(Debug)]210pub struct Code<'s> {211 mkey: &'s str,212 cflags: CFlags,213 width: Width,214 precision: Option<Width>,215 convtype: ConvTypeV,216 caps: bool,217}218pub fn parse_code(str: &str) -> ParseResult<Code> {219 if str.is_empty() {220 return Err(TruncatedFormatCode);221 }222 let (mkey, str) = try_parse_mapping_key(str)?;223 let (cflags, str) = try_parse_cflags(str)?;224 let (width, str) = try_parse_field_width(str)?;225 let (precision, str) = try_parse_precision(str)?;226 let (_, str) = try_parse_length_modifier(str)?;227 let (convtype, str) = parse_conversion_type(str)?;228229 Ok((230 Code {231 mkey,232 cflags,233 width,234 precision,235 convtype: convtype.v,236 caps: convtype.caps,237 },238 str,239 ))240}241242#[derive(Debug)]243pub enum Element<'s> {244 String(&'s str),245 Code(Code<'s>),246}247pub fn parse_codes(mut str: &str) -> Result<Vec<Element>> {248 let mut bytes = str.as_bytes();249 let mut out = vec![];250 let mut offset = 0;251252 loop {253 while offset != bytes.len() && bytes[offset] != b'%' {254 offset += 1;255 }256 if offset != 0 {257 out.push(Element::String(&str[0..offset]));258 }259 if offset == bytes.len() {260 return Ok(out);261 }262 str = &str[offset + 1..];263 let (code, nstr) = parse_code(str)?;264 str = nstr;265 bytes = str.as_bytes();266 offset = 0;267268 out.push(Element::Code(code))269 }270}271272const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";273274#[inline]275pub fn render_integer(276 out: &mut String,277 iv: i64,278 padding: usize,279 precision: usize,280 blank: bool,281 sign: bool,282 radix: i64,283 prefix: &str,284 caps: bool,285) {286 // Digit char indexes in reverse order, i.e287 // for radix = 16 and n = 12f: [15, 2, 1]288 let digits = if iv == 0 {289 vec![0u8]290 } else {291 let mut v = iv.abs();292 let mut nums = Vec::with_capacity(1);293 while v > 0 {294 nums.push((v % radix) as u8);295 v /= radix;296 }297 nums298 };299 let neg = iv < 0;300 let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });301 let zp2 = zp302 .max(precision)303 .saturating_sub(prefix.len() + digits.len());304305 if neg {306 out.push('-')307 } else if sign {308 out.push('+');309 } else if blank {310 out.push(' ');311 }312313 out.reserve(zp2);314 for _ in 0..zp2 {315 out.push('0');316 }317 out.push_str(&prefix);318319 for digit in digits.into_iter().rev() {320 let ch = NUMBERS[digit as usize] as char;321 out.push(if caps { ch.to_ascii_uppercase() } else { ch });322 }323}324325pub fn render_decimal(326 out: &mut String,327 iv: i64,328 padding: usize,329 precision: usize,330 blank: bool,331 sign: bool,332) {333 render_integer(out, iv, padding, precision, blank, sign, 10, "", false)334}335pub fn render_octal(336 out: &mut String,337 iv: i64,338 padding: usize,339 precision: usize,340 alt: bool,341 blank: bool,342 sign: bool,343) {344 render_integer(345 out,346 iv,347 padding,348 precision,349 blank,350 sign,351 8,352 if alt && iv != 0 { "0" } else { "" },353 false,354 )355}356pub fn render_hexadecimal(357 out: &mut String,358 iv: i64,359 padding: usize,360 precision: usize,361 alt: bool,362 blank: bool,363 sign: bool,364 caps: bool,365) {366 render_integer(367 out,368 iv,369 padding,370 precision,371 blank,372 sign,373 16,374 match (alt, caps) {375 (true, true) => "0X",376 (true, false) => "0x",377 (false, _) => "",378 },379 caps,380 )381}382383pub fn render_float(384 out: &mut String,385 n: f64,386 mut padding: usize,387 precision: usize,388 blank: bool,389 sign: bool,390 ensure_pt: bool,391 trailing: bool,392) {393 let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };394 padding = padding.saturating_sub(dot_size + precision);395 render_decimal(out, n.floor() as i64, padding, 0, blank, sign);396 if precision == 0 {397 if ensure_pt {398 out.push('.');399 }400 return;401 }402 let frac = (n.fract() * 10.0_f64.powf(precision as f64) + 0.5).floor();403 if trailing || frac > 0.0 {404 out.push('.');405 let mut frac_str = String::new();406 render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);407 let mut trim = frac_str.len();408 if !trailing {409 for b in frac_str.as_bytes().iter().rev() {410 if *b == b'0' {411 trim -= 1;412 }413 }414 }415 out.push_str(&frac_str[..trim]);416 } else if ensure_pt {417 out.push('.');418 }419}420421pub fn render_float_sci(422 out: &mut String,423 n: f64,424 mut padding: usize,425 precision: usize,426 blank: bool,427 sign: bool,428 ensure_pt: bool,429 trailing: bool,430 caps: bool,431) {432 let exponent = n.log10().floor();433 let mantissa = if exponent as i16 == -324 {434 n * 10.0 / 10.0_f64.powf(exponent + 1.0)435 } else {436 n / 10.0_f64.powf(exponent)437 };438 let mut exponent_str = String::new();439 render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);440441 // +1 for e442 padding = padding.saturating_sub(exponent_str.len() + 1);443444 render_float(445 out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,446 );447 out.push(if caps { 'E' } else { 'e' });448 out.push_str(&exponent_str);449}450451pub fn format_code(452 out: &mut String,453 value: &Val,454 code: &Code,455 width: usize,456 precision: Option<usize>,457) -> Result<()> {458 let clfags = &code.cflags;459 let (fpprec, iprec) = match precision {460 Some(v) => (v, v),461 None => (6, 0),462 };463 let padding = if clfags.zero && !clfags.left {464 width465 } else {466 0467 };468469 // TODO: If left padded, can optimize by writing directly to out470 let mut tmp_out = String::new();471472 match code.convtype {473 ConvTypeV::String => tmp_out.push_str(&value.clone().into_string()?),474 ConvTypeV::Decimal => {475 let value = value.clone().try_cast_num("%d/%u/%i requires number")?;476 render_decimal(477 &mut tmp_out,478 value as i64,479 padding,480 iprec,481 clfags.blank,482 clfags.sign,483 );484 }485 ConvTypeV::Octal => {486 let value = value.clone().try_cast_num("%o requires number")?;487 render_octal(488 &mut tmp_out,489 value as i64,490 padding,491 iprec,492 clfags.alt,493 clfags.blank,494 clfags.sign,495 );496 }497 ConvTypeV::Hexadecimal => {498 let value = value.clone().try_cast_num("%x/%X requires number")?;499 render_hexadecimal(500 &mut tmp_out,501 value as i64,502 padding,503 iprec,504 clfags.alt,505 clfags.blank,506 clfags.sign,507 code.caps,508 );509 }510 ConvTypeV::Scientific => {511 let value = value.clone().try_cast_num("%e/%E requires number")?;512 render_float_sci(513 &mut tmp_out,514 value,515 padding,516 fpprec,517 clfags.blank,518 clfags.sign,519 clfags.alt,520 true,521 code.caps,522 );523 }524 ConvTypeV::Float => {525 let value = value.clone().try_cast_num("%e/%E requires number")?;526 render_float(527 &mut tmp_out,528 value,529 padding,530 fpprec,531 clfags.blank,532 clfags.sign,533 clfags.alt,534 true,535 );536 }537 ConvTypeV::Shorter => {538 let value = value.clone().try_cast_num("%g/%G requires number")?;539 let exponent = value.log10().floor();540 if exponent < -4.0 || exponent >= fpprec as f64 {541 render_float_sci(542 &mut tmp_out,543 value,544 padding,545 fpprec - 1,546 clfags.blank,547 clfags.sign,548 clfags.alt,549 clfags.alt,550 code.caps,551 );552 } else {553 let digits_before_pt = 1.max(exponent as usize + 1);554 render_float(555 &mut tmp_out,556 value,557 padding,558 fpprec - digits_before_pt,559 clfags.blank,560 clfags.sign,561 clfags.alt,562 clfags.alt,563 );564 }565 }566 ConvTypeV::Char => match value.clone().unwrap_if_lazy()? {567 Val::Num(n) => tmp_out.push(568 std::char::from_u32(n as u32)569 .ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,570 ),571 Val::Str(s) => {572 if s.chars().count() != 1 {573 throw!(RuntimeError(574 format!("%c expected 1 char string, got {}", s.chars().count()).into(),575 ));576 }577 tmp_out.push_str(&s);578 }579 _ => {580 throw!(TypeMismatch(581 "%c requires number/string",582 vec![ValType::Num, ValType::Str],583 value.value_type()?,584 ));585 }586 },587 ConvTypeV::Percent => tmp_out.push('%'),588 };589590 let padding = width.saturating_sub(tmp_out.len());591592 if !clfags.left {593 for _ in 0..padding {594 out.push(' ');595 }596 }597 out.push_str(&tmp_out);598 if clfags.left {599 for _ in 0..padding {600 out.push(' ');601 }602 }603604 Ok(())605}606607pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String> {608 let codes = parse_codes(&str)?;609 let mut out = String::new();610611 for code in codes {612 match code {613 Element::String(s) => {614 out.push_str(s);615 }616 Element::Code(c) => {617 let width = match c.width {618 Width::Star => {619 if values.is_empty() {620 throw!(NotEnoughValues);621 }622 let value = &values[0];623 values = &values[1..];624 value.clone().try_cast_num("field width")? as usize625 }626 Width::Fixed(n) => n,627 };628 let precision = match c.precision {629 Some(Width::Star) => {630 if values.is_empty() {631 throw!(NotEnoughValues);632 }633 let value = &values[0];634 values = &values[1..];635 Some(value.clone().try_cast_num("field precision")? as usize)636 }637 Some(Width::Fixed(n)) => Some(n),638 None => None,639 };640641 // %% should not consume a value642 let value = if c.convtype == ConvTypeV::Percent {643 &Val::Null644 } else {645 if values.is_empty() {646 throw!(NotEnoughValues);647 }648 let value = &values[0];649 values = &values[1..];650 value651 };652653 format_code(&mut out, value, &c, width, precision)?;654 }655 }656 }657658 Ok(out)659}660661pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {662 let codes = parse_codes(&str)?;663 let mut out = String::new();664665 for code in codes {666 match code {667 Element::String(s) => {668 out.push_str(s);669 }670 Element::Code(c) => {671 // TODO: Operate on ref672 let f: Rc<str> = c.mkey.into();673 let width = match c.width {674 Width::Star => {675 throw!(CannotUseStarWidthWithObject);676 }677 Width::Fixed(n) => n,678 };679 let precision = match c.precision {680 Some(Width::Star) => {681 throw!(CannotUseStarWidthWithObject);682 }683 Some(Width::Fixed(n)) => Some(n),684 None => None,685 };686687 let value = if c.convtype == ConvTypeV::Percent {688 Val::Null689 } else {690 if f.is_empty() {691 throw!(MappingKeysRequired);692 }693 let value = if let Some(v) = values.get(f.clone())? {694 v695 } else {696 throw!(NoSuchFormatField(f));697 };698 value699 };700701 format_code(&mut out, &value, &c, width, precision)?;702 }703 }704 }705706 Ok(out)707}708709#[cfg(test)]710pub mod test_format {711 use super::*;712713 #[test]714 fn parse() {715 assert_eq!(716 parse_codes(717 "How much error budget is left looking at our %.3f%% availability gurantees?"718 )719 .unwrap()720 .len(),721 4722 );723 }724725 #[test]726 fn octals() {727 assert_eq!(format_arr("%#o", &[Val::Num(8.0)]).unwrap(), "010");728 assert_eq!(format_arr("%#4o", &[Val::Num(8.0)]).unwrap(), " 010");729 assert_eq!(format_arr("%4o", &[Val::Num(8.0)]).unwrap(), " 10");730 assert_eq!(format_arr("%04o", &[Val::Num(8.0)]).unwrap(), "0010");731 assert_eq!(format_arr("%+4o", &[Val::Num(8.0)]).unwrap(), " +10");732 assert_eq!(format_arr("%+04o", &[Val::Num(8.0)]).unwrap(), "+010");733 assert_eq!(format_arr("%-4o", &[Val::Num(8.0)]).unwrap(), "10 ");734 assert_eq!(format_arr("%+-4o", &[Val::Num(8.0)]).unwrap(), "+10 ");735 assert_eq!(format_arr("%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");736 }737738 #[test]739 fn percent_doesnt_consumes_values() {740 assert_eq!(741 format_arr(742 "How much error budget is left looking at our %.3f%% availability gurantees?",743 &[Val::Num(4.0)]744 )745 .unwrap(),746 "How much error budget is left looking at our 4.000% availability gurantees?"747 );748 }749}1//! faster std.format impl2#![allow(clippy::too_many_arguments)]34use crate::{error::Error::*, throw, LocError, ObjValue, Result, Val, ValType};56#[derive(Debug, Clone)]7pub enum FormatError {8 TruncatedFormatCode,9 UnrecognizedConversionType(char),1011 NotEnoughValues,1213 CannotUseStarWidthWithObject,14 MappingKeysRequired,15 NoSuchFormatField(Rc<str>),16}1718impl From<FormatError> for LocError {19 fn from(e: FormatError) -> Self {20 Self::new(Format(e))21 }22}2324use std::rc::Rc;25use FormatError::*;2627type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;2829pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> {30 if str.is_empty() {31 return Err(TruncatedFormatCode);32 }33 let bytes = str.as_bytes();34 if bytes[0] == b'(' {35 let mut i = 1;36 while i < bytes.len() {37 if bytes[i] == b')' {38 return Ok((&str[1..i as usize], &str[i as usize + 1..]));39 }40 i += 1;41 }42 Err(TruncatedFormatCode)43 } else {44 Ok(("", str))45 }46}4748#[cfg(test)]49pub mod tests_key {50 use super::*;5152 #[test]53 fn parse_key() {54 assert_eq!(55 try_parse_mapping_key("(hello ) world").unwrap(),56 ("hello ", " world")57 );58 assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));59 assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));60 assert_eq!(61 try_parse_mapping_key(" () world").unwrap(),62 ("", " () world")63 );64 }6566 #[test]67 #[should_panic]68 fn parse_key_missing_start() {69 try_parse_mapping_key("").unwrap();70 }7172 #[test]73 #[should_panic]74 fn parse_key_missing_end() {75 try_parse_mapping_key("( ").unwrap();76 }77}7879#[derive(Default, Debug)]80pub struct CFlags {81 pub alt: bool,82 pub zero: bool,83 pub left: bool,84 pub blank: bool,85 pub sign: bool,86}8788pub fn try_parse_cflags(str: &str) -> ParseResult<CFlags> {89 if str.is_empty() {90 return Err(TruncatedFormatCode);91 }92 let bytes = str.as_bytes();93 let mut i = 0;94 let mut out = CFlags::default();95 loop {96 if bytes.len() == i {97 return Err(TruncatedFormatCode);98 }99 match bytes[i] {100 b'#' => out.alt = true,101 b'0' => out.zero = true,102 b'-' => out.left = true,103 b' ' => out.blank = true,104 b'+' => out.sign = true,105 _ => break,106 }107 i += 1;108 }109 Ok((out, &str[i..]))110}111112#[derive(Debug, PartialEq)]113pub enum Width {114 Star,115 Fixed(usize),116}117pub fn try_parse_field_width(str: &str) -> ParseResult<Width> {118 if str.is_empty() {119 return Err(TruncatedFormatCode);120 }121 let bytes = str.as_bytes();122 if bytes[0] == b'*' {123 return Ok((Width::Star, &str[1..]));124 }125 let mut out: usize = 0;126 let mut digits = 0;127 while let Some(digit) = (bytes[digits] as char).to_digit(10) {128 out *= 10;129 out += digit as usize;130 digits += 1;131 if digits == bytes.len() {132 return Err(TruncatedFormatCode);133 }134 }135 Ok((Width::Fixed(out), &str[digits..]))136}137138pub fn try_parse_precision(str: &str) -> ParseResult<Option<Width>> {139 if str.is_empty() {140 return Err(TruncatedFormatCode);141 }142 let bytes = str.as_bytes();143 if bytes[0] == b'.' {144 try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))145 } else {146 Ok((None, str))147 }148}149150// Only skips151pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> {152 if str.is_empty() {153 return Err(TruncatedFormatCode);154 }155 let bytes = str.as_bytes();156 let mut idx = 0;157 while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {158 idx += 1;159 if bytes.len() == idx {160 return Err(TruncatedFormatCode);161 }162 }163 Ok(((), &str[idx..]))164}165166#[derive(Debug, PartialEq)]167pub enum ConvTypeV {168 Decimal,169 Octal,170 Hexadecimal,171 Scientific,172 Float,173 Shorter,174 Char,175 String,176 Percent,177}178pub struct ConvType {179 v: ConvTypeV,180 caps: bool,181}182183pub fn parse_conversion_type(str: &str) -> ParseResult<ConvType> {184 if str.is_empty() {185 return Err(TruncatedFormatCode);186 }187188 let code = str.as_bytes()[0];189 let v: (ConvTypeV, bool) = match code {190 b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),191 b'o' => (ConvTypeV::Octal, false),192 b'x' => (ConvTypeV::Hexadecimal, false),193 b'X' => (ConvTypeV::Hexadecimal, true),194 b'e' => (ConvTypeV::Scientific, false),195 b'E' => (ConvTypeV::Scientific, true),196 b'f' => (ConvTypeV::Float, false),197 b'F' => (ConvTypeV::Float, true),198 b'g' => (ConvTypeV::Shorter, false),199 b'G' => (ConvTypeV::Shorter, true),200 b'c' => (ConvTypeV::Char, false),201 b's' => (ConvTypeV::String, false),202 b'%' => (ConvTypeV::Percent, false),203 c => return Err(UnrecognizedConversionType(c as char)),204 };205206 Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))207}208209#[derive(Debug)]210pub struct Code<'s> {211 mkey: &'s str,212 cflags: CFlags,213 width: Width,214 precision: Option<Width>,215 convtype: ConvTypeV,216 caps: bool,217}218pub fn parse_code(str: &str) -> ParseResult<Code> {219 if str.is_empty() {220 return Err(TruncatedFormatCode);221 }222 let (mkey, str) = try_parse_mapping_key(str)?;223 let (cflags, str) = try_parse_cflags(str)?;224 let (width, str) = try_parse_field_width(str)?;225 let (precision, str) = try_parse_precision(str)?;226 let (_, str) = try_parse_length_modifier(str)?;227 let (convtype, str) = parse_conversion_type(str)?;228229 Ok((230 Code {231 mkey,232 cflags,233 width,234 precision,235 convtype: convtype.v,236 caps: convtype.caps,237 },238 str,239 ))240}241242#[derive(Debug)]243pub enum Element<'s> {244 String(&'s str),245 Code(Code<'s>),246}247pub fn parse_codes(mut str: &str) -> Result<Vec<Element>> {248 let mut bytes = str.as_bytes();249 let mut out = vec![];250 let mut offset = 0;251252 loop {253 while offset != bytes.len() && bytes[offset] != b'%' {254 offset += 1;255 }256 if offset != 0 {257 out.push(Element::String(&str[0..offset]));258 }259 if offset == bytes.len() {260 return Ok(out);261 }262 str = &str[offset + 1..];263 let (code, nstr) = parse_code(str)?;264 str = nstr;265 bytes = str.as_bytes();266 offset = 0;267268 out.push(Element::Code(code))269 }270}271272const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";273274#[inline]275pub fn render_integer(276 out: &mut String,277 iv: i64,278 padding: usize,279 precision: usize,280 blank: bool,281 sign: bool,282 radix: i64,283 prefix: &str,284 caps: bool,285) {286 // Digit char indexes in reverse order, i.e287 // for radix = 16 and n = 12f: [15, 2, 1]288 let digits = if iv == 0 {289 vec![0u8]290 } else {291 let mut v = iv.abs();292 let mut nums = Vec::with_capacity(1);293 while v > 0 {294 nums.push((v % radix) as u8);295 v /= radix;296 }297 nums298 };299 let neg = iv < 0;300 let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });301 let zp2 = zp302 .max(precision)303 .saturating_sub(prefix.len() + digits.len());304305 if neg {306 out.push('-')307 } else if sign {308 out.push('+');309 } else if blank {310 out.push(' ');311 }312313 out.reserve(zp2);314 for _ in 0..zp2 {315 out.push('0');316 }317 out.push_str(&prefix);318319 for digit in digits.into_iter().rev() {320 let ch = NUMBERS[digit as usize] as char;321 out.push(if caps { ch.to_ascii_uppercase() } else { ch });322 }323}324325pub fn render_decimal(326 out: &mut String,327 iv: i64,328 padding: usize,329 precision: usize,330 blank: bool,331 sign: bool,332) {333 render_integer(out, iv, padding, precision, blank, sign, 10, "", false)334}335pub fn render_octal(336 out: &mut String,337 iv: i64,338 padding: usize,339 precision: usize,340 alt: bool,341 blank: bool,342 sign: bool,343) {344 render_integer(345 out,346 iv,347 padding,348 precision,349 blank,350 sign,351 8,352 if alt && iv != 0 { "0" } else { "" },353 false,354 )355}356pub fn render_hexadecimal(357 out: &mut String,358 iv: i64,359 padding: usize,360 precision: usize,361 alt: bool,362 blank: bool,363 sign: bool,364 caps: bool,365) {366 render_integer(367 out,368 iv,369 padding,370 precision,371 blank,372 sign,373 16,374 match (alt, caps) {375 (true, true) => "0X",376 (true, false) => "0x",377 (false, _) => "",378 },379 caps,380 )381}382383pub fn render_float(384 out: &mut String,385 n: f64,386 mut padding: usize,387 precision: usize,388 blank: bool,389 sign: bool,390 ensure_pt: bool,391 trailing: bool,392) {393 let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };394 padding = padding.saturating_sub(dot_size + precision);395 render_decimal(out, n.floor() as i64, padding, 0, blank, sign);396 if precision == 0 {397 if ensure_pt {398 out.push('.');399 }400 return;401 }402 let frac = (n.fract() * 10.0_f64.powf(precision as f64) + 0.5).floor();403 if trailing || frac > 0.0 {404 out.push('.');405 let mut frac_str = String::new();406 render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);407 let mut trim = frac_str.len();408 if !trailing {409 for b in frac_str.as_bytes().iter().rev() {410 if *b == b'0' {411 trim -= 1;412 }413 }414 }415 out.push_str(&frac_str[..trim]);416 } else if ensure_pt {417 out.push('.');418 }419}420421pub fn render_float_sci(422 out: &mut String,423 n: f64,424 mut padding: usize,425 precision: usize,426 blank: bool,427 sign: bool,428 ensure_pt: bool,429 trailing: bool,430 caps: bool,431) {432 let exponent = n.log10().floor();433 let mantissa = if exponent as i16 == -324 {434 n * 10.0 / 10.0_f64.powf(exponent + 1.0)435 } else {436 n / 10.0_f64.powf(exponent)437 };438 let mut exponent_str = String::new();439 render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);440441 // +1 for e442 padding = padding.saturating_sub(exponent_str.len() + 1);443444 render_float(445 out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,446 );447 out.push(if caps { 'E' } else { 'e' });448 out.push_str(&exponent_str);449}450451pub fn format_code(452 out: &mut String,453 value: &Val,454 code: &Code,455 width: usize,456 precision: Option<usize>,457) -> Result<()> {458 let clfags = &code.cflags;459 let (fpprec, iprec) = match precision {460 Some(v) => (v, v),461 None => (6, 0),462 };463 let padding = if clfags.zero && !clfags.left {464 width465 } else {466 0467 };468469 // TODO: If left padded, can optimize by writing directly to out470 let mut tmp_out = String::new();471472 match code.convtype {473 ConvTypeV::String => tmp_out.push_str(&value.clone().into_string()?),474 ConvTypeV::Decimal => {475 let value = value.clone().try_cast_num("%d/%u/%i requires number")?;476 render_decimal(477 &mut tmp_out,478 value as i64,479 padding,480 iprec,481 clfags.blank,482 clfags.sign,483 );484 }485 ConvTypeV::Octal => {486 let value = value.clone().try_cast_num("%o requires number")?;487 render_octal(488 &mut tmp_out,489 value as i64,490 padding,491 iprec,492 clfags.alt,493 clfags.blank,494 clfags.sign,495 );496 }497 ConvTypeV::Hexadecimal => {498 let value = value.clone().try_cast_num("%x/%X requires number")?;499 render_hexadecimal(500 &mut tmp_out,501 value as i64,502 padding,503 iprec,504 clfags.alt,505 clfags.blank,506 clfags.sign,507 code.caps,508 );509 }510 ConvTypeV::Scientific => {511 let value = value.clone().try_cast_num("%e/%E requires number")?;512 render_float_sci(513 &mut tmp_out,514 value,515 padding,516 fpprec,517 clfags.blank,518 clfags.sign,519 clfags.alt,520 true,521 code.caps,522 );523 }524 ConvTypeV::Float => {525 let value = value.clone().try_cast_num("%e/%E requires number")?;526 render_float(527 &mut tmp_out,528 value,529 padding,530 fpprec,531 clfags.blank,532 clfags.sign,533 clfags.alt,534 true,535 );536 }537 ConvTypeV::Shorter => {538 let value = value.clone().try_cast_num("%g/%G requires number")?;539 let exponent = value.log10().floor();540 if exponent < -4.0 || exponent >= fpprec as f64 {541 render_float_sci(542 &mut tmp_out,543 value,544 padding,545 fpprec - 1,546 clfags.blank,547 clfags.sign,548 clfags.alt,549 clfags.alt,550 code.caps,551 );552 } else {553 let digits_before_pt = 1.max(exponent as usize + 1);554 render_float(555 &mut tmp_out,556 value,557 padding,558 fpprec - digits_before_pt,559 clfags.blank,560 clfags.sign,561 clfags.alt,562 clfags.alt,563 );564 }565 }566 ConvTypeV::Char => match value.clone().unwrap_if_lazy()? {567 Val::Num(n) => tmp_out.push(568 std::char::from_u32(n as u32)569 .ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,570 ),571 Val::Str(s) => {572 if s.chars().count() != 1 {573 throw!(RuntimeError(574 format!("%c expected 1 char string, got {}", s.chars().count()).into(),575 ));576 }577 tmp_out.push_str(&s);578 }579 _ => {580 throw!(TypeMismatch(581 "%c requires number/string",582 vec![ValType::Num, ValType::Str],583 value.value_type()?,584 ));585 }586 },587 ConvTypeV::Percent => tmp_out.push('%'),588 };589590 let padding = width.saturating_sub(tmp_out.len());591592 if !clfags.left {593 for _ in 0..padding {594 out.push(' ');595 }596 }597 out.push_str(&tmp_out);598 if clfags.left {599 for _ in 0..padding {600 out.push(' ');601 }602 }603604 Ok(())605}606607pub fn format_arr(str: &str, mut values: &[Val]) -> Result<String> {608 let codes = parse_codes(&str)?;609 let mut out = String::new();610611 for code in codes {612 match code {613 Element::String(s) => {614 out.push_str(s);615 }616 Element::Code(c) => {617 let width = match c.width {618 Width::Star => {619 if values.is_empty() {620 throw!(NotEnoughValues);621 }622 let value = &values[0];623 values = &values[1..];624 value.clone().try_cast_num("field width")? as usize625 }626 Width::Fixed(n) => n,627 };628 let precision = match c.precision {629 Some(Width::Star) => {630 if values.is_empty() {631 throw!(NotEnoughValues);632 }633 let value = &values[0];634 values = &values[1..];635 Some(value.clone().try_cast_num("field precision")? as usize)636 }637 Some(Width::Fixed(n)) => Some(n),638 None => None,639 };640641 // %% should not consume a value642 let value = if c.convtype == ConvTypeV::Percent {643 &Val::Null644 } else {645 if values.is_empty() {646 throw!(NotEnoughValues);647 }648 let value = &values[0];649 values = &values[1..];650 value651 };652653 format_code(&mut out, value, &c, width, precision)?;654 }655 }656 }657658 Ok(out)659}660661pub fn format_obj(str: &str, values: &ObjValue) -> Result<String> {662 let codes = parse_codes(&str)?;663 let mut out = String::new();664665 for code in codes {666 match code {667 Element::String(s) => {668 out.push_str(s);669 }670 Element::Code(c) => {671 // TODO: Operate on ref672 let f: Rc<str> = c.mkey.into();673 let width = match c.width {674 Width::Star => {675 throw!(CannotUseStarWidthWithObject);676 }677 Width::Fixed(n) => n,678 };679 let precision = match c.precision {680 Some(Width::Star) => {681 throw!(CannotUseStarWidthWithObject);682 }683 Some(Width::Fixed(n)) => Some(n),684 None => None,685 };686687 let value = if c.convtype == ConvTypeV::Percent {688 Val::Null689 } else {690 if f.is_empty() {691 throw!(MappingKeysRequired);692 }693 let value = if let Some(v) = values.get(f.clone())? {694 v695 } else {696 throw!(NoSuchFormatField(f));697 };698 value699 };700701 format_code(&mut out, &value, &c, width, precision)?;702 }703 }704 }705706 Ok(out)707}708709#[cfg(test)]710pub mod test_format {711 use super::*;712713 #[test]714 fn parse() {715 assert_eq!(716 parse_codes(717 "How much error budget is left looking at our %.3f%% availability gurantees?"718 )719 .unwrap()720 .len(),721 4722 );723 }724725 #[test]726 fn octals() {727 assert_eq!(format_arr("%#o", &[Val::Num(8.0)]).unwrap(), "010");728 assert_eq!(format_arr("%#4o", &[Val::Num(8.0)]).unwrap(), " 010");729 assert_eq!(format_arr("%4o", &[Val::Num(8.0)]).unwrap(), " 10");730 assert_eq!(format_arr("%04o", &[Val::Num(8.0)]).unwrap(), "0010");731 assert_eq!(format_arr("%+4o", &[Val::Num(8.0)]).unwrap(), " +10");732 assert_eq!(format_arr("%+04o", &[Val::Num(8.0)]).unwrap(), "+010");733 assert_eq!(format_arr("%-4o", &[Val::Num(8.0)]).unwrap(), "10 ");734 assert_eq!(format_arr("%+-4o", &[Val::Num(8.0)]).unwrap(), "+10 ");735 assert_eq!(format_arr("%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");736 }737738 #[test]739 fn percent_doesnt_consumes_values() {740 assert_eq!(741 format_arr(742 "How much error budget is left looking at our %.3f%% availability gurantees?",743 &[Val::Num(4.0)]744 )745 .unwrap(),746 "How much error budget is left looking at our 4.000% availability gurantees?"747 );748 }749}crates/jrsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate.rs
@@ -3,7 +3,9 @@
context_creator, equals,
error::Error::*,
escape_string_json, future_wrapper, lazy_val, manifest_json_ex, parse_args, primitive_equals,
- push, throw, with_state, Context, ContextCreator, FuncDesc, LazyBinding, LazyVal, LocError,
+ push, throw,
+ val::ManifestJsonOptions,
+ with_state, Context, ContextCreator, FuncDesc, LazyBinding, LazyVal, LocError, ManifestType,
ObjMember, ObjValue, Result, Val, ValType,
};
use closure::closure;
@@ -405,355 +407,372 @@
let lazy = evaluate(context.clone(), value)?;
let value = lazy.unwrap_if_lazy()?;
Ok(match value {
- Val::Intristic(ns, name) => match (&ns as &str, &name as &str) {
- // arr/string/function
- ("std", "length") => parse_args!(context, "std.length", args, 1, [
- 0, x: [Val::Str|Val::Arr|Val::Obj], vec![ValType::Str, ValType::Arr, ValType::Obj];
- ], {
- Ok(match x {
- Val::Str(n) => Val::Num(n.chars().count() as f64),
- Val::Arr(i) => Val::Num(i.len() as f64),
- Val::Obj(o) => Val::Num(
- o.fields_visibility()
- .into_iter()
- .filter(|(_k, v)| *v)
- .count() as f64,
- ),
- _ => unreachable!(),
- })
- })?,
- // any
- ("std", "type") => parse_args!(context, "std.type", args, 1, [
- 0, x, vec![];
- ], {
- Ok(Val::Str(x.value_type()?.name().into()))
- })?,
- // length, idx=>any
- ("std", "makeArray") => noinline!(parse_args!(context, "std.makeArray", args, 2, [
- 0, sz: [Val::Num]!!Val::Num, vec![ValType::Num];
- 1, func: [Val::Func]!!Val::Func, vec![ValType::Func];
- ], {
- if sz < 0.0 {
- throw!(RuntimeError(format!("makeArray requires size >= 0, got {}", sz).into()));
- }
- let mut out = Vec::with_capacity(sz as usize);
- for i in 0..sz as usize {
- out.push(func.evaluate_values(
- Context::new(),
- &[Val::Num(i as f64)]
- )?)
- }
- Ok(Val::Arr(Rc::new(out)))
- }))?,
- // string
- ("std", "codepoint") => parse_args!(context, "std.codepoint", args, 1, [
- 0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
- ], {
- assert!(
- str.chars().count() == 1,
- "std.codepoint should receive single char string"
- );
- Ok(Val::Num(str.chars().take(1).next().unwrap() as u32 as f64))
- })?,
- // object, includeHidden
- ("std", "objectFieldsEx") => {
- noinline!(parse_args!(context, "std.objectFieldsEx",args, 2, [
- 0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj];
- 1, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool];
- ], {
- let mut out = obj.fields_visibility()
- .into_iter()
- .filter(|(_k, v)| *v || inc_hidden)
- .map(|(k, _v)|k)
- .collect::<Vec<_>>();
- out.sort();
- Ok(Val::Arr(Rc::new(out.into_iter().map(Val::Str).collect())))
- }))?
- }
- // object, field, includeHidden
- ("std", "objectHasEx") => parse_args!(context, "std.objectHasEx", args, 3, [
- 0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj];
- 1, f: [Val::Str]!!Val::Str, vec![ValType::Str];
- 2, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool];
- ], {
- Ok(Val::Bool(
- obj.fields_visibility()
- .into_iter()
- .filter(|(_k, v)| *v || inc_hidden)
- .any(|(k, _v)| *k == *f),
- ))
- })?,
- ("std", "primitiveEquals") => parse_args!(context, "std.primitiveEquals", args, 2, [
- 0, a, vec![];
- 1, b, vec![];
- ], {
- Ok(Val::Bool(primitive_equals(&a, &b)?))
- })?,
- // faster
- ("std", "equals") => parse_args!(context, "std.equals", args, 2, [
- 0, a, vec![];
- 1, b, vec![];
- ], {
- Ok(Val::Bool(equals(&a, &b)?))
- })?,
- ("std", "modulo") => parse_args!(context, "std.modulo", args, 2, [
- 0, a: [Val::Num]!!Val::Num, vec![ValType::Num];
- 1, b: [Val::Num]!!Val::Num, vec![ValType::Num];
- ], {
- Ok(Val::Num(a % b))
- })?,
- ("std", "floor") => parse_args!(context, "std.floor", args, 1, [
- 0, x: [Val::Num]!!Val::Num, vec![ValType::Num];
- ], {
- Ok(Val::Num(x.floor()))
- })?,
- ("std", "log") => parse_args!(context, "std.log", args, 2, [
- 0, n: [Val::Num]!!Val::Num, vec![ValType::Num];
- ], {
- Ok(Val::Num(n.ln()))
- })?,
- ("std", "trace") => parse_args!(context, "std.trace", args, 2, [
- 0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
- 1, rest, vec![];
- ], {
- eprint!("TRACE:");
- if let Some(loc) = loc {
- with_state(|s|{
- let locs = s.map_source_locations(&loc.0, &[loc.1]);
- eprint!(" {}:{}", loc.0.file_name().unwrap().to_str().unwrap(), locs[0].line);
- });
- }
- eprintln!(" {}", str);
- Ok(rest)
- })?,
- ("std", "pow") => parse_args!(context, "std.modulo", args, 2, [
- 0, x: [Val::Num]!!Val::Num, vec![ValType::Num];
- 1, n: [Val::Num]!!Val::Num, vec![ValType::Num];
- ], {
- Ok(Val::Num(x.powf(n)))
- })?,
- ("std", "extVar") => parse_args!(context, "std.extVar", args, 2, [
- 0, x: [Val::Str]!!Val::Str, vec![ValType::Str];
- ], {
- Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or_else(
- || UndefinedExternalVariable(x),
- )?)
- })?,
- ("std", "filter") => noinline!(parse_args!(context, "std.filter", args, 2, [
- 0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
- 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
- ], {
- Ok(Val::Arr(Rc::new(
- arr.iter()
- .cloned()
- .filter(|e| {
- func
- .evaluate_values(context.clone(), &[e.clone()])
- .unwrap()
- .try_cast_bool("filter predicate")
- .unwrap()
+ Val::Intristic(ns, name) => push(
+ loc,
+ || format!("intristic <{}.{}> call", ns, name),
+ || {
+ Ok(match (&ns as &str, &name as &str) {
+ // arr/string/function
+ ("std", "length") => parse_args!(context, "std.length", args, 1, [
+ 0, x: [Val::Str|Val::Arr|Val::Obj], vec![ValType::Str, ValType::Arr, ValType::Obj];
+ ], {
+ Ok(match x {
+ Val::Str(n) => Val::Num(n.chars().count() as f64),
+ Val::Arr(i) => Val::Num(i.len() as f64),
+ Val::Obj(o) => Val::Num(
+ o.fields_visibility()
+ .into_iter()
+ .filter(|(_k, v)| *v)
+ .count() as f64,
+ ),
+ _ => unreachable!(),
})
- .collect(),
- )))
- }))?,
- // faster
- ("std", "foldl") => noinline!(parse_args!(context, "std.foldl", args, 3, [
- 0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
- 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
- 2, init, vec![];
- ], {
- let mut acc = init;
- for i in arr.iter().cloned() {
- acc = func.evaluate_values(context.clone(), &[acc, i])?;
- }
- Ok(acc)
- }))?,
- // faster
- ("std", "foldr") => noinline!(parse_args!(context, "std.foldr", args, 3, [
- 0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
- 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
- 2, init, vec![];
- ], {
- let mut acc = init;
- for i in arr.iter().rev().cloned() {
- acc = func.evaluate_values(context.clone(), &[acc, i])?;
- }
- Ok(acc)
- }))?,
- // faster
- #[allow(non_snake_case)]
- ("std", "sortImpl") => noinline!(parse_args!(context, "std.sort", args, 2, [
- 0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
- 1, keyF: [Val::Func]!!Val::Func, vec![ValType::Func];
- ], {
- if arr.len() <= 1 {
- return Ok(Val::Arr(arr))
- }
- let mut new_arr = arr.iter().cloned().collect::<Vec<_>>();
- match keyF.evaluate_values(context.clone(), &[new_arr[0].clone()])? {
- Val::Str(_) => {
- let mut err = None;
- new_arr.sort_by_cached_key(|k| {
- match keyF.evaluate_values(context.clone(), &[k.clone()]) {
- Ok(Val::Str(v)) => v,
- Ok(_) => {
- err = Some(LocError::new(RuntimeError("types of all array elements should equal".into())));
- "".into()
- }
- Err(e) => {
- err = Some(e);
- "".into()
- }
+ })?,
+ // any
+ ("std", "type") => parse_args!(context, "std.type", args, 1, [
+ 0, x, vec![];
+ ], {
+ Ok(Val::Str(x.value_type()?.name().into()))
+ })?,
+ // length, idx=>any
+ ("std", "makeArray") => {
+ noinline!(parse_args!(context, "std.makeArray", args, 2, [
+ 0, sz: [Val::Num]!!Val::Num, vec![ValType::Num];
+ 1, func: [Val::Func]!!Val::Func, vec![ValType::Func];
+ ], {
+ if sz < 0.0 {
+ throw!(RuntimeError(format!("makeArray requires size >= 0, got {}", sz).into()));
}
- });
- if let Some(e) = err {
- return Err(e);
+ let mut out = Vec::with_capacity(sz as usize);
+ for i in 0..sz as usize {
+ out.push(func.evaluate_values(
+ Context::new(),
+ &[Val::Num(i as f64)]
+ )?)
+ }
+ Ok(Val::Arr(Rc::new(out)))
+ }))?
+ }
+ // string
+ ("std", "codepoint") => parse_args!(context, "std.codepoint", args, 1, [
+ 0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+ ], {
+ assert!(
+ str.chars().count() == 1,
+ "std.codepoint should receive single char string"
+ );
+ Ok(Val::Num(str.chars().take(1).next().unwrap() as u32 as f64))
+ })?,
+ // object, includeHidden
+ ("std", "objectFieldsEx") => {
+ noinline!(parse_args!(context, "std.objectFieldsEx",args, 2, [
+ 0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj];
+ 1, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool];
+ ], {
+ let mut out = obj.fields_visibility()
+ .into_iter()
+ .filter(|(_k, v)| *v || inc_hidden)
+ .map(|(k, _v)|k)
+ .collect::<Vec<_>>();
+ out.sort();
+ Ok(Val::Arr(Rc::new(out.into_iter().map(Val::Str).collect())))
+ }))?
+ }
+ // object, field, includeHidden
+ ("std", "objectHasEx") => parse_args!(context, "std.objectHasEx", args, 3, [
+ 0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj];
+ 1, f: [Val::Str]!!Val::Str, vec![ValType::Str];
+ 2, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool];
+ ], {
+ Ok(Val::Bool(
+ obj.fields_visibility()
+ .into_iter()
+ .filter(|(_k, v)| *v || inc_hidden)
+ .any(|(k, _v)| *k == *f),
+ ))
+ })?,
+ ("std", "primitiveEquals") => {
+ parse_args!(context, "std.primitiveEquals", args, 2, [
+ 0, a, vec![];
+ 1, b, vec![];
+ ], {
+ Ok(Val::Bool(primitive_equals(&a, &b)?))
+ })?
+ }
+ // faster
+ ("std", "equals") => parse_args!(context, "std.equals", args, 2, [
+ 0, a, vec![];
+ 1, b, vec![];
+ ], {
+ Ok(Val::Bool(equals(&a, &b)?))
+ })?,
+ ("std", "modulo") => parse_args!(context, "std.modulo", args, 2, [
+ 0, a: [Val::Num]!!Val::Num, vec![ValType::Num];
+ 1, b: [Val::Num]!!Val::Num, vec![ValType::Num];
+ ], {
+ Ok(Val::Num(a % b))
+ })?,
+ ("std", "floor") => parse_args!(context, "std.floor", args, 1, [
+ 0, x: [Val::Num]!!Val::Num, vec![ValType::Num];
+ ], {
+ Ok(Val::Num(x.floor()))
+ })?,
+ ("std", "log") => parse_args!(context, "std.log", args, 2, [
+ 0, n: [Val::Num]!!Val::Num, vec![ValType::Num];
+ ], {
+ Ok(Val::Num(n.ln()))
+ })?,
+ ("std", "trace") => parse_args!(context, "std.trace", args, 2, [
+ 0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+ 1, rest, vec![];
+ ], {
+ eprint!("TRACE:");
+ if let Some(loc) = loc {
+ with_state(|s|{
+ let locs = s.map_source_locations(&loc.0, &[loc.1]);
+ eprint!(" {}:{}", loc.0.file_name().unwrap().to_str().unwrap(), locs[0].line);
+ });
}
- },
- Val::Num(_) => {
- let mut err = None;
- new_arr.sort_unstable_by(|a, b| {
- match (keyF.evaluate_values(context.clone(), &[a.clone()]), keyF.evaluate_values(context.clone(), &[b.clone()])) {
- (Ok(Val::Num(a)), Ok(Val::Num(b))) => a.partial_cmp(&b).unwrap(),
- (Ok(_a), Ok(_b)) => {
- err = Some(RuntimeError("types of all array elements should equal".into()).into());
- Ordering::Equal
+ eprintln!(" {}", str);
+ Ok(rest)
+ })?,
+ ("std", "pow") => parse_args!(context, "std.modulo", args, 2, [
+ 0, x: [Val::Num]!!Val::Num, vec![ValType::Num];
+ 1, n: [Val::Num]!!Val::Num, vec![ValType::Num];
+ ], {
+ Ok(Val::Num(x.powf(n)))
+ })?,
+ ("std", "extVar") => parse_args!(context, "std.extVar", args, 2, [
+ 0, x: [Val::Str]!!Val::Str, vec![ValType::Str];
+ ], {
+ Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or_else(
+ || UndefinedExternalVariable(x),
+ )?)
+ })?,
+ ("std", "filter") => noinline!(parse_args!(context, "std.filter", args, 2, [
+ 0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
+ 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
+ ], {
+ Ok(Val::Arr(Rc::new(
+ arr.iter()
+ .cloned()
+ .filter(|e| {
+ func
+ .evaluate_values(context.clone(), &[e.clone()])
+ .unwrap()
+ .try_cast_bool("filter predicate")
+ .unwrap()
+ })
+ .collect(),
+ )))
+ }))?,
+ // faster
+ ("std", "foldl") => noinline!(parse_args!(context, "std.foldl", args, 3, [
+ 0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
+ 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
+ 2, init, vec![];
+ ], {
+ let mut acc = init;
+ for i in arr.iter().cloned() {
+ acc = func.evaluate_values(context.clone(), &[acc, i])?;
+ }
+ Ok(acc)
+ }))?,
+ // faster
+ ("std", "foldr") => noinline!(parse_args!(context, "std.foldr", args, 3, [
+ 0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
+ 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
+ 2, init, vec![];
+ ], {
+ let mut acc = init;
+ for i in arr.iter().rev().cloned() {
+ acc = func.evaluate_values(context.clone(), &[acc, i])?;
+ }
+ Ok(acc)
+ }))?,
+ // faster
+ #[allow(non_snake_case)]
+ ("std", "sortImpl") => noinline!(parse_args!(context, "std.sort", args, 2, [
+ 0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
+ 1, keyF: [Val::Func]!!Val::Func, vec![ValType::Func];
+ ], {
+ if arr.len() <= 1 {
+ return Ok(Val::Arr(arr))
+ }
+ let mut new_arr = arr.iter().cloned().collect::<Vec<_>>();
+ match keyF.evaluate_values(context.clone(), &[new_arr[0].clone()])? {
+ Val::Str(_) => {
+ let mut err = None;
+ new_arr.sort_by_cached_key(|k| {
+ match keyF.evaluate_values(context.clone(), &[k.clone()]) {
+ Ok(Val::Str(v)) => v,
+ Ok(_) => {
+ err = Some(LocError::new(RuntimeError("types of all array elements should equal".into())));
+ "".into()
+ }
+ Err(e) => {
+ err = Some(e);
+ "".into()
+ }
+ }
+ });
+ if let Some(e) = err {
+ return Err(e);
}
- (Err(e), _) | (_, Err(e)) => {
- err = Some(e);
- Ordering::Equal
+ },
+ Val::Num(_) => {
+ let mut err = None;
+ new_arr.sort_unstable_by(|a, b| {
+ match (keyF.evaluate_values(context.clone(), &[a.clone()]), keyF.evaluate_values(context.clone(), &[b.clone()])) {
+ (Ok(Val::Num(a)), Ok(Val::Num(b))) => a.partial_cmp(&b).unwrap(),
+ (Ok(_a), Ok(_b)) => {
+ err = Some(RuntimeError("types of all array elements should equal".into()).into());
+ Ordering::Equal
+ }
+ (Err(e), _) | (_, Err(e)) => {
+ err = Some(e);
+ Ordering::Equal
+ }
+ }
+ });
+ if let Some(e) = err {
+ return Err(e);
}
- }
- });
- if let Some(e) = err {
- return Err(e);
+ },
+ _ => throw!(RuntimeError("keys should be number or string".into()))
}
- },
- _ => throw!(RuntimeError("keys should be number or string".into()))
- }
- Ok(Val::Arr(Rc::new(new_arr)))
- }))?,
- // faster
- ("std", "format") => parse_args!(context, "std.format", args, 2, [
- 0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
- 1, vals, vec![]
- ], {
- push(&Some(ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0)), ||format!("std.format of {}", str), ||{
- Ok(match vals {
- Val::Arr(vals) => Val::Str(format_arr(&str, &vals)?.into()),
- Val::Obj(obj) => Val::Str(format_obj(&str, &obj)?.into()),
- o => Val::Str(format_arr(&str, &[o])?.into()),
- })
- })
- })?,
- // faster
- ("std", "range") => parse_args!(context, "std.range", args, 2, [
- 0, from: [Val::Num]!!Val::Num, vec![ValType::Num];
- 0, to: [Val::Num]!!Val::Num, vec![ValType::Num];
- ], {
- let mut out = Vec::with_capacity((1+to as usize-from as usize).max(0));
- for i in from as usize..=to as usize {
- out.push(Val::Num(i as f64));
- }
- Ok(Val::Arr(Rc::new(out)))
- })?,
- ("std", "char") => parse_args!(context, "std.char", args, 1, [
- 0, n: [Val::Num]!!Val::Num, vec![ValType::Num];
- ], {
- let mut out = String::new();
- out.push(std::char::from_u32(n as u32).ok_or_else(||
- InvalidUnicodeCodepointGot(n as u32)
- )?);
- Ok(Val::Str(out.into()))
- })?,
- ("std", "encodeUTF8") => parse_args!(context, "std.encodeUtf8", args, 1, [
- 0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
- ], {
- Ok(Val::Arr(Rc::new(str.bytes().map(|b| Val::Num(b as f64)).collect())))
- })?,
- ("std", "md5") => noinline!(parse_args!(context, "std.md5", args, 1, [
- 0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
- ], {
- Ok(Val::Str(format!("{:x}", md5::compute(&str.as_bytes())).into()))
- }))?,
- // faster
- ("std", "base64") => parse_args!(context, "std.base64", args, 1, [
- 0, input: [Val::Str | Val::Arr], vec![ValType::Arr, ValType::Str];
- ], {
- Ok(Val::Str(match input {
- Val::Str(s) => {
- base64::encode(s.bytes().collect::<Vec<_>>()).into()
- },
- Val::Arr(a) => {
- base64::encode(a.iter().map(|v| {
- Ok(v.clone().try_cast_num("base64 array")? as u8)
- }).collect::<Result<Vec<_>>>()?).into()
- },
- _ => unreachable!()
- }))
- })?,
- // faster
- ("std", "join") => noinline!(parse_args!(context, "std.join", args, 2, [
- 0, sep: [Val::Str|Val::Arr], vec![ValType::Str, ValType::Arr];
- 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
- ], {
- Ok(match sep {
- Val::Arr(joiner_items) => {
- let mut out = Vec::new();
+ Ok(Val::Arr(Rc::new(new_arr)))
+ }))?,
+ // faster
+ ("std", "format") => parse_args!(context, "std.format", args, 2, [
+ 0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+ 1, vals, vec![]
+ ], {
+ push(&Some(ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0)), ||format!("std.format of {}", str), ||{
+ Ok(match vals {
+ Val::Arr(vals) => Val::Str(format_arr(&str, &vals)?.into()),
+ Val::Obj(obj) => Val::Str(format_obj(&str, &obj)?.into()),
+ o => Val::Str(format_arr(&str, &[o])?.into()),
+ })
+ })
+ })?,
+ // faster
+ ("std", "range") => parse_args!(context, "std.range", args, 2, [
+ 0, from: [Val::Num]!!Val::Num, vec![ValType::Num];
+ 1, to: [Val::Num]!!Val::Num, vec![ValType::Num];
+ ], {
+ let mut out = Vec::with_capacity((1+to as usize-from as usize).max(0));
+ for i in from as usize..=to as usize {
+ out.push(Val::Num(i as f64));
+ }
+ Ok(Val::Arr(Rc::new(out)))
+ })?,
+ ("std", "char") => parse_args!(context, "std.char", args, 1, [
+ 0, n: [Val::Num]!!Val::Num, vec![ValType::Num];
+ ], {
+ let mut out = String::new();
+ out.push(std::char::from_u32(n as u32).ok_or_else(||
+ InvalidUnicodeCodepointGot(n as u32)
+ )?);
+ Ok(Val::Str(out.into()))
+ })?,
+ ("std", "encodeUTF8") => parse_args!(context, "std.encodeUtf8", args, 1, [
+ 0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+ ], {
+ Ok(Val::Arr(Rc::new(str.bytes().map(|b| Val::Num(b as f64)).collect())))
+ })?,
+ ("std", "md5") => noinline!(parse_args!(context, "std.md5", args, 1, [
+ 0, str: [Val::Str]!!Val::Str, vec![ValType::Str];
+ ], {
+ Ok(Val::Str(format!("{:x}", md5::compute(&str.as_bytes())).into()))
+ }))?,
+ // faster
+ ("std", "base64") => parse_args!(context, "std.base64", args, 1, [
+ 0, input: [Val::Str | Val::Arr], vec![ValType::Arr, ValType::Str];
+ ], {
+ Ok(Val::Str(match input {
+ Val::Str(s) => {
+ base64::encode(s.bytes().collect::<Vec<_>>()).into()
+ },
+ Val::Arr(a) => {
+ base64::encode(a.iter().map(|v| {
+ Ok(v.clone().try_cast_num("base64 array")? as u8)
+ }).collect::<Result<Vec<_>>>()?).into()
+ },
+ _ => unreachable!()
+ }))
+ })?,
+ // faster
+ ("std", "join") => noinline!(parse_args!(context, "std.join", args, 2, [
+ 0, sep: [Val::Str|Val::Arr], vec![ValType::Str, ValType::Arr];
+ 1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
+ ], {
+ Ok(match sep {
+ Val::Arr(joiner_items) => {
+ let mut out = Vec::new();
- let mut first = true;
- for item in arr.iter().cloned() {
- if let Val::Arr(items) = item.unwrap_if_lazy()? {
- if !first {
- out.reserve(joiner_items.len());
- out.extend(joiner_items.iter().cloned());
+ let mut first = true;
+ for item in arr.iter().cloned() {
+ if let Val::Arr(items) = item.unwrap_if_lazy()? {
+ if !first {
+ out.reserve(joiner_items.len());
+ out.extend(joiner_items.iter().cloned());
+ }
+ first = false;
+ out.reserve(items.len());
+ out.extend(items.iter().cloned());
+ } else {
+ throw!(RuntimeError("in std.join all items should be arrays".into()));
+ }
}
- first = false;
- out.reserve(items.len());
- out.extend(items.iter().cloned());
- } else {
- throw!(RuntimeError("in std.join all items should be arrays".into()));
- }
- }
- Val::Arr(Rc::new(out))
- },
- Val::Str(sep) => {
- let mut out = String::new();
+ Val::Arr(Rc::new(out))
+ },
+ Val::Str(sep) => {
+ let mut out = String::new();
- let mut first = true;
- for item in arr.iter().cloned() {
- if let Val::Str(item) = item.unwrap_if_lazy()? {
- if !first {
- out += &sep;
+ let mut first = true;
+ for item in arr.iter().cloned() {
+ if let Val::Str(item) = item.unwrap_if_lazy()? {
+ if !first {
+ out += &sep;
+ }
+ first = false;
+ out += &item;
+ } else {
+ throw!(RuntimeError("in std.join all items should be strings".into()));
+ }
}
- first = false;
- out += &item;
- } else {
- throw!(RuntimeError("in std.join all items should be strings".into()));
- }
- }
- Val::Str(out.into())
- },
- _ => unreachable!()
+ Val::Str(out.into())
+ },
+ _ => unreachable!()
+ })
+ }))?,
+ // Faster
+ ("std", "escapeStringJson") => {
+ parse_args!(context, "std.escapeStringJson", args, 1, [
+ 0, str_: [Val::Str]!!Val::Str, vec![ValType::Str];
+ ], {
+ Ok(Val::Str(escape_string_json(&str_).into()))
+ })?
+ }
+ // Faster
+ ("std", "manifestJsonEx") => {
+ parse_args!(context, "std.manifestJsonEx", args, 2, [
+ 0, value, vec![];
+ 1, indent: [Val::Str]!!Val::Str, vec![ValType::Str];
+ ], {
+ Ok(Val::Str(manifest_json_ex(&value, &ManifestJsonOptions {
+ padding: &indent,
+ mtype: ManifestType::Std,
+ })?.into()))
+ })?
+ }
+ (ns, name) => throw!(IntristicNotFound(ns.into(), name.into())),
})
- }))?,
- // Faster
- ("std", "escapeStringJson") => parse_args!(context, "std.escapeStringJson", args, 1, [
- 0, str_: [Val::Str]!!Val::Str, vec![ValType::Str];
- ], {
- Ok(Val::Str(escape_string_json(&str_).into()))
- })?,
- // Faster
- ("std", "manifestJsonEx") => parse_args!(context, "std.manifestJsonEx", args, 2, [
- 0, value, vec![];
- 1, indent: [Val::Str]!!Val::Str, vec![ValType::Str];
- ], {
- Ok(Val::Str(manifest_json_ex(&value, &indent)?.into()))
- })?,
- (ns, name) => throw!(IntristicNotFound(ns.into(), name.into())),
- },
+ },
+ )?,
Val::Func(f) => {
let body = || f.evaluate(context, args, tailstrict);
if tailstrict {