difftreelog
refactor move modules from root
in: master
31 files changed
bindings/jsonnet/src/native.rsdiffbeforeafterboth--- a/bindings/jsonnet/src/native.rs
+++ b/bindings/jsonnet/src/native.rs
@@ -8,9 +8,8 @@
use gcmodule::Cc;
use jrsonnet_evaluator::{
error::{Error, LocError},
- function::BuiltinParam,
+ function::builtin::{BuiltinParam, NativeCallback, NativeCallbackHandler},
gc::TraceBox,
- native::{NativeCallback, NativeCallbackHandler},
typed::Typed,
IStr, State, Val,
};
crates/jrsonnet-evaluator/src/builtin/format.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/builtin/format.rs
+++ /dev/null
@@ -1,796 +0,0 @@
-//! faster std.format impl
-#![allow(clippy::too_many_arguments)]
-
-use gcmodule::Trace;
-use jrsonnet_interner::IStr;
-use jrsonnet_types::ValType;
-use thiserror::Error;
-
-use crate::{error::Error::*, throw, typed::Typed, LocError, ObjValue, Result, State, Val};
-
-#[derive(Debug, Clone, Error, Trace)]
-pub enum FormatError {
- #[error("truncated format code")]
- TruncatedFormatCode,
- #[error("unrecognized conversion type: {0}")]
- UnrecognizedConversionType(char),
-
- #[error("not enough values")]
- NotEnoughValues,
-
- #[error("cannot use * width with object")]
- CannotUseStarWidthWithObject,
- #[error("mapping keys required")]
- MappingKeysRequired,
- #[error("no such format field: {0}")]
- NoSuchFormatField(IStr),
-}
-
-impl From<FormatError> for LocError {
- fn from(e: FormatError) -> Self {
- Self::new(Format(e))
- }
-}
-
-use FormatError::*;
-
-type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;
-
-pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> {
- if str.is_empty() {
- return Err(TruncatedFormatCode);
- }
- let bytes = str.as_bytes();
- if bytes[0] == b'(' {
- let mut i = 1;
- while i < bytes.len() {
- if bytes[i] == b')' {
- return Ok((&str[1..i as usize], &str[i as usize + 1..]));
- }
- i += 1;
- }
- Err(TruncatedFormatCode)
- } else {
- Ok(("", str))
- }
-}
-
-#[cfg(test)]
-pub mod tests_key {
- use super::*;
-
- #[test]
- fn parse_key() {
- assert_eq!(
- try_parse_mapping_key("(hello ) world").unwrap(),
- ("hello ", " world")
- );
- assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));
- assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));
- assert_eq!(
- try_parse_mapping_key(" () world").unwrap(),
- ("", " () world")
- );
- }
-
- #[test]
- #[should_panic]
- fn parse_key_missing_start() {
- try_parse_mapping_key("").unwrap();
- }
-
- #[test]
- #[should_panic]
- fn parse_key_missing_end() {
- try_parse_mapping_key("( ").unwrap();
- }
-}
-
-#[allow(clippy::struct_excessive_bools)]
-#[derive(Default, Debug)]
-pub struct CFlags {
- pub alt: bool,
- pub zero: bool,
- pub left: bool,
- pub blank: bool,
- pub sign: bool,
-}
-
-pub fn try_parse_cflags(str: &str) -> ParseResult<CFlags> {
- if str.is_empty() {
- return Err(TruncatedFormatCode);
- }
- let bytes = str.as_bytes();
- let mut i = 0;
- let mut out = CFlags::default();
- loop {
- if bytes.len() == i {
- return Err(TruncatedFormatCode);
- }
- match bytes[i] {
- b'#' => out.alt = true,
- b'0' => out.zero = true,
- b'-' => out.left = true,
- b' ' => out.blank = true,
- b'+' => out.sign = true,
- _ => break,
- }
- i += 1;
- }
- Ok((out, &str[i..]))
-}
-
-#[derive(Debug, PartialEq)]
-pub enum Width {
- Star,
- Fixed(usize),
-}
-pub fn try_parse_field_width(str: &str) -> ParseResult<Width> {
- if str.is_empty() {
- return Err(TruncatedFormatCode);
- }
- let bytes = str.as_bytes();
- if bytes[0] == b'*' {
- return Ok((Width::Star, &str[1..]));
- }
- let mut out: usize = 0;
- let mut digits = 0;
- while let Some(digit) = (bytes[digits] as char).to_digit(10) {
- out *= 10;
- out += digit as usize;
- digits += 1;
- if digits == bytes.len() {
- return Err(TruncatedFormatCode);
- }
- }
- Ok((Width::Fixed(out), &str[digits..]))
-}
-
-pub fn try_parse_precision(str: &str) -> ParseResult<Option<Width>> {
- if str.is_empty() {
- return Err(TruncatedFormatCode);
- }
- let bytes = str.as_bytes();
- if bytes[0] == b'.' {
- try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))
- } else {
- Ok((None, str))
- }
-}
-
-// Only skips
-pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> {
- if str.is_empty() {
- return Err(TruncatedFormatCode);
- }
- let bytes = str.as_bytes();
- let mut idx = 0;
- while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {
- idx += 1;
- if bytes.len() == idx {
- return Err(TruncatedFormatCode);
- }
- }
- Ok(((), &str[idx..]))
-}
-
-#[derive(Debug, PartialEq)]
-pub enum ConvTypeV {
- Decimal,
- Octal,
- Hexadecimal,
- Scientific,
- Float,
- Shorter,
- Char,
- String,
- Percent,
-}
-pub struct ConvType {
- v: ConvTypeV,
- caps: bool,
-}
-
-pub fn parse_conversion_type(str: &str) -> ParseResult<ConvType> {
- if str.is_empty() {
- return Err(TruncatedFormatCode);
- }
-
- let code = str.as_bytes()[0];
- let v: (ConvTypeV, bool) = match code {
- b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),
- b'o' => (ConvTypeV::Octal, false),
- b'x' => (ConvTypeV::Hexadecimal, false),
- b'X' => (ConvTypeV::Hexadecimal, true),
- b'e' => (ConvTypeV::Scientific, false),
- b'E' => (ConvTypeV::Scientific, true),
- b'f' => (ConvTypeV::Float, false),
- b'F' => (ConvTypeV::Float, true),
- b'g' => (ConvTypeV::Shorter, false),
- b'G' => (ConvTypeV::Shorter, true),
- b'c' => (ConvTypeV::Char, false),
- b's' => (ConvTypeV::String, false),
- b'%' => (ConvTypeV::Percent, false),
- c => return Err(UnrecognizedConversionType(c as char)),
- };
-
- Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))
-}
-
-#[derive(Debug)]
-pub struct Code<'s> {
- mkey: &'s str,
- cflags: CFlags,
- width: Width,
- precision: Option<Width>,
- convtype: ConvTypeV,
- caps: bool,
-}
-pub fn parse_code(str: &str) -> ParseResult<Code> {
- if str.is_empty() {
- return Err(TruncatedFormatCode);
- }
- let (mkey, str) = try_parse_mapping_key(str)?;
- let (cflags, str) = try_parse_cflags(str)?;
- let (width, str) = try_parse_field_width(str)?;
- let (precision, str) = try_parse_precision(str)?;
- let (_, str) = try_parse_length_modifier(str)?;
- let (convtype, str) = parse_conversion_type(str)?;
-
- Ok((
- Code {
- mkey,
- cflags,
- width,
- precision,
- convtype: convtype.v,
- caps: convtype.caps,
- },
- str,
- ))
-}
-
-#[derive(Debug)]
-pub enum Element<'s> {
- String(&'s str),
- Code(Code<'s>),
-}
-pub fn parse_codes(mut str: &str) -> Result<Vec<Element>> {
- let mut bytes = str.as_bytes();
- let mut out = vec![];
- let mut offset = 0;
-
- loop {
- while offset != bytes.len() && bytes[offset] != b'%' {
- offset += 1;
- }
- if offset != 0 {
- out.push(Element::String(&str[0..offset]));
- }
- if offset == bytes.len() {
- return Ok(out);
- }
- str = &str[offset + 1..];
- let code;
- (code, str) = parse_code(str)?;
- bytes = str.as_bytes();
- offset = 0;
-
- out.push(Element::Code(code));
- }
-}
-
-const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
-
-#[inline]
-pub fn render_integer(
- out: &mut String,
- iv: i64,
- padding: usize,
- precision: usize,
- blank: bool,
- sign: bool,
- radix: i64,
- prefix: &str,
- caps: bool,
-) {
- // Digit char indexes in reverse order, i.e
- // for radix = 16 and n = 12f: [15, 2, 1]
- let digits = if iv == 0 {
- vec![0u8]
- } else {
- let mut v = iv.abs();
- let mut nums = Vec::with_capacity(1);
- while v > 0 {
- nums.push((v % radix) as u8);
- v /= radix;
- }
- nums
- };
- let neg = iv < 0;
- let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });
- let zp2 = zp
- .max(precision)
- .saturating_sub(prefix.len() + digits.len());
-
- if neg {
- out.push('-');
- } else if sign {
- out.push('+');
- } else if blank {
- out.push(' ');
- }
-
- out.reserve(zp2);
- for _ in 0..zp2 {
- out.push('0');
- }
- out.push_str(prefix);
-
- for digit in digits.into_iter().rev() {
- let ch = NUMBERS[digit as usize] as char;
- out.push(if caps { ch.to_ascii_uppercase() } else { ch });
- }
-}
-
-pub fn render_decimal(
- out: &mut String,
- iv: i64,
- padding: usize,
- precision: usize,
- blank: bool,
- sign: bool,
-) {
- render_integer(out, iv, padding, precision, blank, sign, 10, "", false);
-}
-pub fn render_octal(
- out: &mut String,
- iv: i64,
- padding: usize,
- precision: usize,
- alt: bool,
- blank: bool,
- sign: bool,
-) {
- render_integer(
- out,
- iv,
- padding,
- precision,
- blank,
- sign,
- 8,
- if alt && iv != 0 { "0" } else { "" },
- false,
- );
-}
-
-#[allow(clippy::fn_params_excessive_bools)]
-pub fn render_hexadecimal(
- out: &mut String,
- iv: i64,
- padding: usize,
- precision: usize,
- alt: bool,
- blank: bool,
- sign: bool,
- caps: bool,
-) {
- render_integer(
- out,
- iv,
- padding,
- precision,
- blank,
- sign,
- 16,
- match (alt, caps) {
- (true, true) => "0X",
- (true, false) => "0x",
- (false, _) => "",
- },
- caps,
- );
-}
-
-#[allow(clippy::fn_params_excessive_bools)]
-pub fn render_float(
- out: &mut String,
- n: f64,
- mut padding: usize,
- precision: usize,
- blank: bool,
- sign: bool,
- ensure_pt: bool,
- trailing: bool,
-) {
- let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };
- padding = padding.saturating_sub(dot_size + precision);
- render_decimal(out, n.floor() as i64, padding, 0, blank, sign);
- if precision == 0 {
- if ensure_pt {
- out.push('.');
- }
- return;
- }
- let frac = n
- .fract()
- .mul_add(10.0_f64.powf(precision as f64), 0.5)
- .floor();
- if trailing || frac > 0.0 {
- out.push('.');
- let mut frac_str = String::new();
- render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);
- let mut trim = frac_str.len();
- if !trailing {
- for b in frac_str.as_bytes().iter().rev() {
- if *b == b'0' {
- trim -= 1;
- }
- }
- }
- out.push_str(&frac_str[..trim]);
- } else if ensure_pt {
- out.push('.');
- }
-}
-
-#[allow(clippy::fn_params_excessive_bools)]
-pub fn render_float_sci(
- out: &mut String,
- n: f64,
- mut padding: usize,
- precision: usize,
- blank: bool,
- sign: bool,
- ensure_pt: bool,
- trailing: bool,
- caps: bool,
-) {
- let exponent = n.log10().floor();
- let mantissa = if exponent as i16 == -324 {
- n * 10.0 / 10.0_f64.powf(exponent + 1.0)
- } else {
- n / 10.0_f64.powf(exponent)
- };
- let mut exponent_str = String::new();
- render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);
-
- // +1 for e
- padding = padding.saturating_sub(exponent_str.len() + 1);
-
- render_float(
- out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,
- );
- out.push(if caps { 'E' } else { 'e' });
- out.push_str(&exponent_str);
-}
-
-#[allow(clippy::too_many_lines)]
-pub fn format_code(
- s: State,
- out: &mut String,
- value: &Val,
- code: &Code,
- width: usize,
- precision: Option<usize>,
-) -> Result<()> {
- let clfags = &code.cflags;
- let (fpprec, iprec) = match precision {
- Some(v) => (v, v),
- None => (6, 0),
- };
- let padding = if clfags.zero && !clfags.left {
- width
- } else {
- 0
- };
-
- // TODO: If left padded, can optimize by writing directly to out
- let mut tmp_out = String::new();
-
- match code.convtype {
- ConvTypeV::String => tmp_out.push_str(&value.clone().to_string(s)?),
- ConvTypeV::Decimal => {
- let value = f64::from_untyped(value.clone(), s)?;
- render_decimal(
- &mut tmp_out,
- value as i64,
- padding,
- iprec,
- clfags.blank,
- clfags.sign,
- );
- }
- ConvTypeV::Octal => {
- let value = f64::from_untyped(value.clone(), s)?;
- render_octal(
- &mut tmp_out,
- value as i64,
- padding,
- iprec,
- clfags.alt,
- clfags.blank,
- clfags.sign,
- );
- }
- ConvTypeV::Hexadecimal => {
- let value = f64::from_untyped(value.clone(), s)?;
- render_hexadecimal(
- &mut tmp_out,
- value as i64,
- padding,
- iprec,
- clfags.alt,
- clfags.blank,
- clfags.sign,
- code.caps,
- );
- }
- ConvTypeV::Scientific => {
- let value = f64::from_untyped(value.clone(), s)?;
- render_float_sci(
- &mut tmp_out,
- value,
- padding,
- fpprec,
- clfags.blank,
- clfags.sign,
- clfags.alt,
- true,
- code.caps,
- );
- }
- ConvTypeV::Float => {
- let value = f64::from_untyped(value.clone(), s)?;
- render_float(
- &mut tmp_out,
- value,
- padding,
- fpprec,
- clfags.blank,
- clfags.sign,
- clfags.alt,
- true,
- );
- }
- ConvTypeV::Shorter => {
- let value = f64::from_untyped(value.clone(), s)?;
- let exponent = value.log10().floor();
- if exponent < -4.0 || exponent >= fpprec as f64 {
- render_float_sci(
- &mut tmp_out,
- value,
- padding,
- fpprec - 1,
- clfags.blank,
- clfags.sign,
- clfags.alt,
- clfags.alt,
- code.caps,
- );
- } else {
- let digits_before_pt = 1.max(exponent as usize + 1);
- render_float(
- &mut tmp_out,
- value,
- padding,
- fpprec - digits_before_pt,
- clfags.blank,
- clfags.sign,
- clfags.alt,
- clfags.alt,
- );
- }
- }
- ConvTypeV::Char => match value.clone() {
- Val::Num(n) => tmp_out
- .push(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?),
- Val::Str(s) => {
- if s.chars().count() != 1 {
- throw!(RuntimeError(
- format!("%c expected 1 char string, got {}", s.chars().count()).into(),
- ));
- }
- tmp_out.push_str(&s);
- }
- _ => {
- throw!(TypeMismatch(
- "%c requires number/string",
- vec![ValType::Num, ValType::Str],
- value.value_type(),
- ));
- }
- },
- ConvTypeV::Percent => tmp_out.push('%'),
- };
-
- let padding = width.saturating_sub(tmp_out.len());
-
- if !clfags.left {
- for _ in 0..padding {
- out.push(' ');
- }
- }
- out.push_str(&tmp_out);
- if clfags.left {
- for _ in 0..padding {
- out.push(' ');
- }
- }
-
- Ok(())
-}
-
-pub fn format_arr(s: State, str: &str, mut values: &[Val]) -> Result<String> {
- let codes = parse_codes(str)?;
- let mut out = String::new();
-
- for code in codes {
- match code {
- Element::String(s) => {
- out.push_str(s);
- }
- Element::Code(c) => {
- let width = match c.width {
- Width::Star => {
- if values.is_empty() {
- throw!(NotEnoughValues);
- }
- let value = &values[0];
- values = &values[1..];
- usize::from_untyped(value.clone(), s.clone())?
- }
- Width::Fixed(n) => n,
- };
- let precision = match c.precision {
- Some(Width::Star) => {
- if values.is_empty() {
- throw!(NotEnoughValues);
- }
- let value = &values[0];
- values = &values[1..];
- Some(usize::from_untyped(value.clone(), s.clone())?)
- }
- Some(Width::Fixed(n)) => Some(n),
- None => None,
- };
-
- // %% should not consume a value
- let value = if c.convtype == ConvTypeV::Percent {
- &Val::Null
- } else {
- if values.is_empty() {
- throw!(NotEnoughValues);
- }
- let value = &values[0];
- values = &values[1..];
- value
- };
-
- format_code(s.clone(), &mut out, value, &c, width, precision)?;
- }
- }
- }
-
- Ok(out)
-}
-
-pub fn format_obj(s: State, str: &str, values: &ObjValue) -> Result<String> {
- let codes = parse_codes(str)?;
- let mut out = String::new();
-
- for code in codes {
- match code {
- Element::String(s) => {
- out.push_str(s);
- }
- Element::Code(c) => {
- // TODO: Operate on ref
- let f: IStr = c.mkey.into();
- let width = match c.width {
- Width::Star => {
- throw!(CannotUseStarWidthWithObject);
- }
- Width::Fixed(n) => n,
- };
- let precision = match c.precision {
- Some(Width::Star) => {
- throw!(CannotUseStarWidthWithObject);
- }
- Some(Width::Fixed(n)) => Some(n),
- None => None,
- };
-
- let value = if c.convtype == ConvTypeV::Percent {
- Val::Null
- } else {
- if f.is_empty() {
- throw!(MappingKeysRequired);
- }
- if let Some(v) = values.get(s.clone(), f.clone())? {
- v
- } else {
- throw!(NoSuchFormatField(f));
- }
- };
-
- format_code(s.clone(), &mut out, &value, &c, width, precision)?;
- }
- }
- }
-
- Ok(out)
-}
-
-#[cfg(test)]
-pub mod test_format {
- use super::*;
-
- #[test]
- fn parse() {
- assert_eq!(
- parse_codes(
- "How much error budget is left looking at our %.3f%% availability gurantees?"
- )
- .unwrap()
- .len(),
- 4
- );
- }
-
- #[test]
- fn octals() {
- let s = State::default();
- assert_eq!(
- format_arr(s.clone(), "%#o", &[Val::Num(8.0)]).unwrap(),
- "010"
- );
- assert_eq!(
- format_arr(s.clone(), "%#4o", &[Val::Num(8.0)]).unwrap(),
- " 010"
- );
- assert_eq!(
- format_arr(s.clone(), "%4o", &[Val::Num(8.0)]).unwrap(),
- " 10"
- );
- assert_eq!(
- format_arr(s.clone(), "%04o", &[Val::Num(8.0)]).unwrap(),
- "0010"
- );
- assert_eq!(
- format_arr(s.clone(), "%+4o", &[Val::Num(8.0)]).unwrap(),
- " +10"
- );
- assert_eq!(
- format_arr(s.clone(), "%+04o", &[Val::Num(8.0)]).unwrap(),
- "+010"
- );
- assert_eq!(
- format_arr(s.clone(), "%-4o", &[Val::Num(8.0)]).unwrap(),
- "10 "
- );
- assert_eq!(
- format_arr(s.clone(), "%+-4o", &[Val::Num(8.0)]).unwrap(),
- "+10 "
- );
- assert_eq!(
- format_arr(s.clone(), "%+-04o", &[Val::Num(8.0)]).unwrap(),
- "+10 "
- );
- }
-
- #[test]
- fn percent_doesnt_consumes_values() {
- let s = State::default();
- assert_eq!(
- format_arr(
- s,
- "How much error budget is left looking at our %.3f%% availability gurantees?",
- &[Val::Num(4.0)]
- )
- .unwrap(),
- "How much error budget is left looking at our 4.000% availability gurantees?"
- );
- }
-}
crates/jrsonnet-evaluator/src/builtin/manifest.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs
+++ /dev/null
@@ -1,347 +0,0 @@
-use crate::{
- error::{Error::*, Result},
- throw, State, Val,
-};
-
-#[derive(PartialEq, Clone, Copy)]
-pub enum ManifestType {
- // Applied in manifestification
- Manifest,
- /// Used for std.manifestJson
- /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest
- Std,
- /// No line breaks, used in `obj+''`
- ToString,
- /// Minified json
- Minify,
-}
-
-pub struct ManifestJsonOptions<'s> {
- pub padding: &'s str,
- pub mtype: ManifestType,
- pub newline: &'s str,
- pub key_val_sep: &'s str,
- #[cfg(feature = "exp-preserve-order")]
- pub preserve_order: bool,
-}
-
-pub fn manifest_json_ex(s: State, val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {
- let mut out = String::new();
- manifest_json_ex_buf(s, val, &mut out, &mut String::new(), options)?;
- Ok(out)
-}
-fn manifest_json_ex_buf(
- s: State,
- val: &Val,
- buf: &mut String,
- cur_padding: &mut String,
- options: &ManifestJsonOptions<'_>,
-) -> Result<()> {
- use std::fmt::Write;
- let mtype = options.mtype;
- match val {
- Val::Bool(v) => {
- if *v {
- buf.push_str("true");
- } else {
- buf.push_str("false");
- }
- }
- Val::Null => buf.push_str("null"),
- Val::Str(s) => escape_string_json_buf(s, buf),
- Val::Num(n) => write!(buf, "{}", n).unwrap(),
- Val::Arr(items) => {
- buf.push('[');
- if !items.is_empty() {
- if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
- buf.push_str(options.newline);
- }
-
- let old_len = cur_padding.len();
- cur_padding.push_str(options.padding);
- for (i, item) in items.iter(s.clone()).enumerate() {
- if i != 0 {
- buf.push(',');
- if mtype == ManifestType::ToString {
- buf.push(' ');
- } else if mtype != ManifestType::Minify {
- buf.push_str(options.newline);
- }
- }
- buf.push_str(cur_padding);
- manifest_json_ex_buf(s.clone(), &item?, buf, cur_padding, options)?;
- }
- cur_padding.truncate(old_len);
-
- if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
- buf.push_str(options.newline);
- buf.push_str(cur_padding);
- }
- } else if mtype == ManifestType::Std {
- buf.push_str("\n\n");
- buf.push_str(cur_padding);
- } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {
- buf.push(' ');
- }
- buf.push(']');
- }
- Val::Obj(obj) => {
- obj.run_assertions(s.clone())?;
- buf.push('{');
- let fields = obj.fields(
- #[cfg(feature = "exp-preserve-order")]
- options.preserve_order,
- );
- if !fields.is_empty() {
- if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
- buf.push_str(options.newline);
- }
-
- let old_len = cur_padding.len();
- cur_padding.push_str(options.padding);
- for (i, field) in fields.into_iter().enumerate() {
- if i != 0 {
- buf.push(',');
- if mtype == ManifestType::ToString {
- buf.push(' ');
- } else if mtype != ManifestType::Minify {
- buf.push_str(options.newline);
- }
- }
- buf.push_str(cur_padding);
- escape_string_json_buf(&field, buf);
- buf.push_str(options.key_val_sep);
- s.push_description(
- || format!("field <{}> manifestification", field.clone()),
- || {
- let value = obj.get(s.clone(), field.clone())?.unwrap();
- manifest_json_ex_buf(s.clone(), &value, buf, cur_padding, options)?;
- Ok(Val::Null)
- },
- )?;
- }
- cur_padding.truncate(old_len);
-
- if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
- buf.push_str(options.newline);
- buf.push_str(cur_padding);
- }
- } else if mtype == ManifestType::Std {
- buf.push_str("\n\n");
- buf.push_str(cur_padding);
- } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {
- buf.push(' ');
- }
- buf.push('}');
- }
- Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),
- };
- Ok(())
-}
-
-pub fn escape_string_json(s: &str) -> String {
- let mut buf = String::new();
- escape_string_json_buf(s, &mut buf);
- buf
-}
-
-fn escape_string_json_buf(s: &str, buf: &mut String) {
- use std::fmt::Write;
- buf.push('"');
- for c in s.chars() {
- match c {
- '"' => buf.push_str("\\\""),
- '\\' => buf.push_str("\\\\"),
- '\u{0008}' => buf.push_str("\\b"),
- '\u{000c}' => buf.push_str("\\f"),
- '\n' => buf.push_str("\\n"),
- '\r' => buf.push_str("\\r"),
- '\t' => buf.push_str("\\t"),
- c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {
- write!(buf, "\\u{:04x}", c as u32).unwrap();
- }
- c => buf.push(c),
- }
- }
- buf.push('"');
-}
-
-pub struct ManifestYamlOptions<'s> {
- /// Padding before fields, i.e
- /// ```yaml
- /// a:
- /// b:
- /// ## <- this
- /// ```
- pub padding: &'s str,
- /// Padding before array elements in objects
- /// ```yaml
- /// a:
- /// - 1
- /// ## <- this
- /// ```
- pub arr_element_padding: &'s str,
- /// Should yaml keys appear unescaped, when possible
- /// ```yaml
- /// "safe_key": 1
- /// # vs
- /// safe_key: 1
- /// ```
- pub quote_keys: bool,
- /// If true - then order of fields is preserved as written,
- /// instead of sorting alphabetically
- #[cfg(feature = "exp-preserve-order")]
- pub preserve_order: bool,
-}
-
-/// From <https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289>
-/// With added date check
-fn yaml_needs_quotes(string: &str) -> bool {
- fn need_quotes_spaces(string: &str) -> bool {
- string.starts_with(' ') || string.ends_with(' ')
- }
-
- string.is_empty()
- || need_quotes_spaces(string)
- || string.starts_with(|c| matches!(c, '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'))
- || string.contains(|c| matches!(c, ':' | '{' | '}' | '[' | ']' | ',' | '#' | '`' | '\"' | '\'' | '\\' | '\0'..='\x06' | '\t' | '\n' | '\r' | '\x0e'..='\x1a' | '\x1c'..='\x1f'))
- || [
- // http://yaml.org/type/bool.html
- // Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse
- // them as string, not booleans, although it is violating the YAML 1.1 specification.
- // See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088.
- "yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false",
- "on", "On", "ON", "off", "Off", "OFF", // http://yaml.org/type/null.html
- "null", "Null", "NULL", "~",
- ].contains(&string)
- || (string.chars().all(|c| matches!(c, '0'..='9' | '-'))
- && string.chars().filter(|c| *c == '-').count() == 2)
- || string.starts_with('.')
- || string.starts_with("0x")
- || string.parse::<i64>().is_ok()
- || string.parse::<f64>().is_ok()
-}
-
-pub fn manifest_yaml_ex(s: State, val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {
- let mut out = String::new();
- manifest_yaml_ex_buf(s, val, &mut out, &mut String::new(), options)?;
- Ok(out)
-}
-
-#[allow(clippy::too_many_lines)]
-fn manifest_yaml_ex_buf(
- s: State,
- val: &Val,
- buf: &mut String,
- cur_padding: &mut String,
- options: &ManifestYamlOptions<'_>,
-) -> Result<()> {
- use std::fmt::Write;
- match val {
- Val::Bool(v) => {
- if *v {
- buf.push_str("true");
- } else {
- buf.push_str("false");
- }
- }
- Val::Null => buf.push_str("null"),
- Val::Str(s) => {
- if s.is_empty() {
- buf.push_str("\"\"");
- } else if let Some(s) = s.strip_suffix('\n') {
- buf.push('|');
- for line in s.split('\n') {
- buf.push('\n');
- buf.push_str(cur_padding);
- buf.push_str(options.padding);
- buf.push_str(line);
- }
- } else if !options.quote_keys && !yaml_needs_quotes(s) {
- buf.push_str(s);
- } else {
- escape_string_json_buf(s, buf);
- }
- }
- Val::Num(n) => write!(buf, "{}", *n).unwrap(),
- Val::Arr(a) => {
- if a.is_empty() {
- buf.push_str("[]");
- } else {
- for (i, item) in a.iter(s.clone()).enumerate() {
- if i != 0 {
- buf.push('\n');
- buf.push_str(cur_padding);
- }
- let item = item?;
- buf.push('-');
- match &item {
- Val::Arr(a) if !a.is_empty() => {
- buf.push('\n');
- buf.push_str(cur_padding);
- buf.push_str(options.padding);
- }
- _ => buf.push(' '),
- }
- let extra_padding = match &item {
- Val::Arr(a) => !a.is_empty(),
- Val::Obj(o) => !o.is_empty(),
- _ => false,
- };
- let prev_len = cur_padding.len();
- if extra_padding {
- cur_padding.push_str(options.padding);
- }
- manifest_yaml_ex_buf(s.clone(), &item, buf, cur_padding, options)?;
- cur_padding.truncate(prev_len);
- }
- }
- }
- Val::Obj(o) => {
- if o.is_empty() {
- buf.push_str("{}");
- } else {
- for (i, key) in o
- .fields(
- #[cfg(feature = "exp-preserve-order")]
- options.preserve_order,
- )
- .iter()
- .enumerate()
- {
- if i != 0 {
- buf.push('\n');
- buf.push_str(cur_padding);
- }
- if !options.quote_keys && !yaml_needs_quotes(key) {
- buf.push_str(key);
- } else {
- escape_string_json_buf(key, buf);
- }
- buf.push(':');
- let prev_len = cur_padding.len();
- let item = o.get(s.clone(), key.clone())?.expect("field exists");
- match &item {
- Val::Arr(a) if !a.is_empty() => {
- buf.push('\n');
- buf.push_str(cur_padding);
- buf.push_str(options.arr_element_padding);
- cur_padding.push_str(options.arr_element_padding);
- }
- Val::Obj(o) if !o.is_empty() => {
- buf.push('\n');
- buf.push_str(cur_padding);
- buf.push_str(options.padding);
- cur_padding.push_str(options.padding);
- }
- _ => buf.push(' '),
- }
- manifest_yaml_ex_buf(s.clone(), &item, buf, cur_padding, options)?;
- cur_padding.truncate(prev_len);
- }
- }
- }
- Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),
- }
- Ok(())
-}
crates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ /dev/null
@@ -1,749 +0,0 @@
-// All builtins should return results
-#![allow(clippy::unnecessary_wraps)]
-
-use std::collections::HashMap;
-
-use format::{format_arr, format_obj};
-use gcmodule::Cc;
-use jrsonnet_interner::IStr;
-use serde::Deserialize;
-use serde_yaml::DeserializingQuirks;
-
-use crate::{
- builtin::manifest::{manifest_yaml_ex, ManifestYamlOptions},
- error::{Error::*, Result},
- function::{CallLocation, StaticBuiltin},
- operator::evaluate_mod_op,
- throw,
- typed::{Any, BoundedUsize, Bytes, Either2, Either4, PositiveF64, Typed, VecVal, M1},
- val::{equals, primitive_equals, ArrValue, FuncVal, IndexableVal, Slice},
- Either, ObjValue, State, Val,
-};
-
-pub mod stdlib;
-pub use stdlib::*;
-
-use self::manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType};
-
-pub mod format;
-pub mod manifest;
-pub mod sort;
-
-pub fn std_format(s: State, str: IStr, vals: Val) -> Result<String> {
- s.push(
- CallLocation::native(),
- || format!("std.format of {}", str),
- || {
- Ok(match vals {
- Val::Arr(vals) => format_arr(s.clone(), &str, &vals.evaluated(s.clone())?)?,
- Val::Obj(obj) => format_obj(s.clone(), &str, &obj)?,
- o => format_arr(s.clone(), &str, &[o])?,
- })
- },
- )
-}
-
-pub fn std_slice(
- indexable: IndexableVal,
- index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
- end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
- step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
-) -> Result<Val> {
- match &indexable {
- IndexableVal::Str(s) => {
- let index = index.as_deref().copied().unwrap_or(0);
- let end = end.as_deref().copied().unwrap_or(usize::MAX);
- let step = step.as_deref().copied().unwrap_or(1);
-
- if index >= end {
- return Ok(Val::Str("".into()));
- }
-
- Ok(Val::Str(
- (s.chars()
- .skip(index)
- .take(end - index)
- .step_by(step)
- .collect::<String>())
- .into(),
- ))
- }
- IndexableVal::Arr(arr) => {
- let index = index.as_deref().copied().unwrap_or(0);
- let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());
- let step = step.as_deref().copied().unwrap_or(1);
-
- if index >= end {
- return Ok(Val::Arr(ArrValue::new_eager()));
- }
-
- Ok(Val::Arr(ArrValue::Slice(Box::new(Slice {
- inner: arr.clone(),
- from: index as u32,
- to: end as u32,
- step: step as u32,
- }))))
- }
- }
-}
-
-type BuiltinsType = HashMap<IStr, &'static dyn StaticBuiltin>;
-
-thread_local! {
- pub static BUILTINS: BuiltinsType = {
- [
- ("length".into(), builtin_length::INST),
- ("type".into(), builtin_type::INST),
- ("makeArray".into(), builtin_make_array::INST),
- ("codepoint".into(), builtin_codepoint::INST),
- ("objectFieldsEx".into(), builtin_object_fields_ex::INST),
- ("objectHasEx".into(), builtin_object_has_ex::INST),
- ("slice".into(), builtin_slice::INST),
- ("substr".into(), builtin_substr::INST),
- ("primitiveEquals".into(), builtin_primitive_equals::INST),
- ("equals".into(), builtin_equals::INST),
- ("modulo".into(), builtin_modulo::INST),
- ("mod".into(), builtin_mod::INST),
- ("floor".into(), builtin_floor::INST),
- ("ceil".into(), builtin_ceil::INST),
- ("log".into(), builtin_log::INST),
- ("pow".into(), builtin_pow::INST),
- ("sqrt".into(), builtin_sqrt::INST),
- ("sin".into(), builtin_sin::INST),
- ("cos".into(), builtin_cos::INST),
- ("tan".into(), builtin_tan::INST),
- ("asin".into(), builtin_asin::INST),
- ("acos".into(), builtin_acos::INST),
- ("atan".into(), builtin_atan::INST),
- ("exp".into(), builtin_exp::INST),
- ("mantissa".into(), builtin_mantissa::INST),
- ("exponent".into(), builtin_exponent::INST),
- ("extVar".into(), builtin_ext_var::INST),
- ("native".into(), builtin_native::INST),
- ("filter".into(), builtin_filter::INST),
- ("map".into(), builtin_map::INST),
- ("flatMap".into(), builtin_flatmap::INST),
- ("foldl".into(), builtin_foldl::INST),
- ("foldr".into(), builtin_foldr::INST),
- ("sort".into(), builtin_sort::INST),
- ("format".into(), builtin_format::INST),
- ("range".into(), builtin_range::INST),
- ("char".into(), builtin_char::INST),
- ("encodeUTF8".into(), builtin_encode_utf8::INST),
- ("decodeUTF8".into(), builtin_decode_utf8::INST),
- ("md5".into(), builtin_md5::INST),
- ("base64".into(), builtin_base64::INST),
- ("base64DecodeBytes".into(), builtin_base64_decode_bytes::INST),
- ("base64Decode".into(), builtin_base64_decode::INST),
- ("trace".into(), builtin_trace::INST),
- ("join".into(), builtin_join::INST),
- ("escapeStringJson".into(), builtin_escape_string_json::INST),
- ("manifestJsonEx".into(), builtin_manifest_json_ex::INST),
- ("manifestYamlDoc".into(), builtin_manifest_yaml_doc::INST),
- ("reverse".into(), builtin_reverse::INST),
- ("id".into(), builtin_id::INST),
- ("strReplace".into(), builtin_str_replace::INST),
- ("splitLimit".into(), builtin_splitlimit::INST),
- ("parseJson".into(), builtin_parse_json::INST),
- ("parseYaml".into(), builtin_parse_yaml::INST),
- ("asciiUpper".into(), builtin_ascii_upper::INST),
- ("asciiLower".into(), builtin_ascii_lower::INST),
- ("member".into(), builtin_member::INST),
- ("count".into(), builtin_count::INST),
- ("any".into(), builtin_any::INST),
- ("all".into(), builtin_all::INST),
- ].iter().cloned().collect()
- };
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> Result<usize> {
- use Either4::*;
- Ok(match x {
- A(x) => x.chars().count(),
- B(x) => x.len(),
- C(x) => x.len(),
- D(f) => f.args_len(),
- })
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_type(x: Any) -> Result<IStr> {
- Ok(x.0.value_type().name().into())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_make_array(s: State, sz: usize, func: FuncVal) -> Result<VecVal> {
- let mut out = Vec::with_capacity(sz);
- for i in 0..sz {
- out.push(func.evaluate_simple(s.clone(), &[i as f64].as_slice())?);
- }
- Ok(VecVal(Cc::new(out)))
-}
-
-#[jrsonnet_macros::builtin]
-const fn builtin_codepoint(str: char) -> Result<u32> {
- Ok(str as u32)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_object_fields_ex(
- obj: ObjValue,
- inc_hidden: bool,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Result<VecVal> {
- #[cfg(feature = "exp-preserve-order")]
- let preserve_order = preserve_order.unwrap_or(false);
- let out = obj.fields_ex(
- inc_hidden,
- #[cfg(feature = "exp-preserve-order")]
- preserve_order,
- );
- Ok(VecVal(Cc::new(
- out.into_iter().map(Val::Str).collect::<Vec<_>>(),
- )))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_object_has_ex(obj: ObjValue, f: IStr, inc_hidden: bool) -> Result<bool> {
- Ok(obj.has_field_ex(f, inc_hidden))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_parse_json(st: State, s: IStr) -> Result<Any> {
- use serde_json::Value;
- let value: Value = serde_json::from_str(&s)
- .map_err(|e| RuntimeError(format!("failed to parse json: {}", e).into()))?;
- Ok(Any(Value::into_untyped(value, st)?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_parse_yaml(st: State, s: IStr) -> Result<Any> {
- use serde_json::Value;
- let value = serde_yaml::Deserializer::from_str_with_quirks(
- &s,
- DeserializingQuirks { old_octals: true },
- );
- let mut out = vec![];
- for item in value {
- let value = Value::deserialize(item)
- .map_err(|e| RuntimeError(format!("failed to parse yaml: {}", e).into()))?;
- let val = Value::into_untyped(value, st.clone())?;
- out.push(val);
- }
- Ok(Any(if out.is_empty() {
- Val::Null
- } else if out.len() == 1 {
- out.into_iter().next().unwrap()
- } else {
- Val::Arr(out.into())
- }))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_slice(
- indexable: IndexableVal,
- index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
- end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
- step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
-) -> Result<Any> {
- std_slice(indexable, index, end, step).map(Any)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_substr(str: IStr, from: usize, len: usize) -> Result<String> {
- Ok(str.chars().skip(from as usize).take(len as usize).collect())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_primitive_equals(a: Any, b: Any) -> Result<bool> {
- primitive_equals(&a.0, &b.0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_equals(s: State, a: Any, b: Any) -> Result<bool> {
- equals(s, &a.0, &b.0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_modulo(a: f64, b: f64) -> Result<f64> {
- Ok(a % b)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_mod(s: State, a: Either![f64, IStr], b: Any) -> Result<Any> {
- use Either2::*;
- Ok(Any(evaluate_mod_op(
- s,
- &match a {
- A(v) => Val::Num(v),
- B(s) => Val::Str(s),
- },
- &b.0,
- )?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_floor(x: f64) -> Result<f64> {
- Ok(x.floor())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ceil(x: f64) -> Result<f64> {
- Ok(x.ceil())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_log(n: f64) -> Result<f64> {
- Ok(n.ln())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_pow(x: f64, n: f64) -> Result<f64> {
- Ok(x.powf(n))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_sqrt(x: PositiveF64) -> Result<f64> {
- Ok(x.0.sqrt())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_sin(x: f64) -> Result<f64> {
- Ok(x.sin())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_cos(x: f64) -> Result<f64> {
- Ok(x.cos())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_tan(x: f64) -> Result<f64> {
- Ok(x.tan())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_asin(x: f64) -> Result<f64> {
- Ok(x.asin())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_acos(x: f64) -> Result<f64> {
- Ok(x.acos())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_atan(x: f64) -> Result<f64> {
- Ok(x.atan())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_exp(x: f64) -> Result<f64> {
- Ok(x.exp())
-}
-
-fn frexp(s: f64) -> (f64, i16) {
- if 0.0 == s {
- (s, 0)
- } else {
- let lg = s.abs().log2();
- let x = (lg - lg.floor() - 1.0).exp2();
- let exp = lg.floor() + 1.0;
- (s.signum() * x, exp as i16)
- }
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_mantissa(x: f64) -> Result<f64> {
- Ok(frexp(x).0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_exponent(x: f64) -> Result<i16> {
- Ok(frexp(x).1)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ext_var(s: State, x: IStr) -> Result<Any> {
- Ok(Any(s
- .settings()
- .ext_vars
- .get(&x)
- .cloned()
- .ok_or(UndefinedExternalVariable(x))?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_native(s: State, name: IStr) -> Result<Any> {
- Ok(Any(s
- .settings()
- .ext_natives
- .get(&name)
- .cloned()
- .map_or(Val::Null, |v| {
- Val::Func(FuncVal::Builtin(v.clone()))
- })))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_filter(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
- arr.filter(s.clone(), |val| {
- bool::from_untyped(
- func.evaluate_simple(s.clone(), &[Any(val.clone())].as_slice())?,
- s.clone(),
- )
- })
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_map(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
- arr.map(s.clone(), |val| {
- func.evaluate_simple(s.clone(), &[Any(val)].as_slice())
- })
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_flatmap(s: State, func: FuncVal, arr: IndexableVal) -> Result<IndexableVal> {
- match arr {
- IndexableVal::Str(str) => {
- let mut out = String::new();
- for c in str.chars() {
- match func.evaluate_simple(s.clone(), &[c.to_string()].as_slice())? {
- Val::Str(o) => out.push_str(&o),
- Val::Null => continue,
- _ => throw!(RuntimeError(
- "in std.join all items should be strings".into()
- )),
- };
- }
- Ok(IndexableVal::Str(out.into()))
- }
- IndexableVal::Arr(a) => {
- let mut out = Vec::new();
- for el in a.iter(s.clone()) {
- let el = el?;
- match func.evaluate_simple(s.clone(), &[Any(el)].as_slice())? {
- Val::Arr(o) => {
- for oe in o.iter(s.clone()) {
- out.push(oe?);
- }
- }
- Val::Null => continue,
- _ => throw!(RuntimeError(
- "in std.join all items should be arrays".into()
- )),
- };
- }
- Ok(IndexableVal::Arr(out.into()))
- }
- }
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_foldl(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
- let mut acc = init.0;
- for i in arr.iter(s.clone()) {
- acc = func.evaluate_simple(s.clone(), &[Any(acc), Any(i?)].as_slice())?;
- }
- Ok(Any(acc))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_foldr(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
- let mut acc = init.0;
- for i in arr.iter(s.clone()).rev() {
- acc = func.evaluate_simple(s.clone(), &[Any(i?), Any(acc)].as_slice())?;
- }
- Ok(Any(acc))
-}
-
-#[jrsonnet_macros::builtin]
-#[allow(non_snake_case)]
-fn builtin_sort(s: State, arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
- if arr.len() <= 1 {
- return Ok(arr);
- }
- Ok(ArrValue::Eager(sort::sort(
- s.clone(),
- arr.evaluated(s)?,
- keyF.as_ref(),
- )?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_format(s: State, str: IStr, vals: Any) -> Result<String> {
- std_format(s, str, vals.0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_range(from: i32, to: i32) -> Result<ArrValue> {
- if to < from {
- return Ok(ArrValue::new_eager());
- }
- Ok(ArrValue::new_range(from, to))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_char(n: u32) -> Result<char> {
- Ok(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_encode_utf8(str: IStr) -> Result<Bytes> {
- Ok(Bytes(str.bytes().collect::<Vec<u8>>().into()))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_decode_utf8(arr: Bytes) -> Result<IStr> {
- Ok(std::str::from_utf8(&arr.0)
- .map_err(|_| RuntimeError("bad utf8".into()))?
- .into())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_md5(str: IStr) -> Result<String> {
- Ok(format!("{:x}", md5::compute(&str.as_bytes())))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_trace(s: State, loc: CallLocation, str: IStr, rest: Any) -> Result<Any> {
- eprint!("TRACE:");
- if let Some(loc) = loc.0 {
- 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) as Result<Any>
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_base64(input: Either![Bytes, IStr]) -> Result<String> {
- use Either2::*;
- Ok(match input {
- A(a) => base64::encode(a.0),
- B(l) => base64::encode(l.bytes().collect::<Vec<_>>()),
- })
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_base64_decode_bytes(input: IStr) -> Result<Bytes> {
- Ok(Bytes(
- base64::decode(&input.as_bytes())
- .map_err(|_| RuntimeError("bad base64".into()))?
- .into(),
- ))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_base64_decode(input: IStr) -> Result<String> {
- let bytes = base64::decode(&input.as_bytes()).map_err(|_| RuntimeError("bad base64".into()))?;
- Ok(String::from_utf8(bytes).map_err(|_| RuntimeError("bad utf8".into()))?)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_join(s: State, sep: IndexableVal, arr: ArrValue) -> Result<IndexableVal> {
- Ok(match sep {
- IndexableVal::Arr(joiner_items) => {
- let mut out = Vec::new();
-
- let mut first = true;
- for item in arr.iter(s.clone()) {
- let item = item?.clone();
- if let Val::Arr(items) = item {
- if !first {
- out.reserve(joiner_items.len());
- // TODO: extend
- for item in joiner_items.iter(s.clone()) {
- out.push(item?);
- }
- }
- first = false;
- out.reserve(items.len());
- for item in items.iter(s.clone()) {
- out.push(item?);
- }
- } else if matches!(item, Val::Null) {
- continue;
- } else {
- throw!(RuntimeError(
- "in std.join all items should be arrays".into()
- ));
- }
- }
-
- IndexableVal::Arr(out.into())
- }
- IndexableVal::Str(sep) => {
- let mut out = String::new();
-
- let mut first = true;
- for item in arr.iter(s) {
- let item = item?.clone();
- if let Val::Str(item) = item {
- if !first {
- out += &sep;
- }
- first = false;
- out += &item;
- } else if matches!(item, Val::Null) {
- continue;
- } else {
- throw!(RuntimeError(
- "in std.join all items should be strings".into()
- ));
- }
- }
-
- IndexableVal::Str(out.into())
- }
- })
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_escape_string_json(str_: IStr) -> Result<String> {
- Ok(escape_string_json(&str_))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_manifest_json_ex(
- s: State,
- value: Any,
- indent: IStr,
- newline: Option<IStr>,
- key_val_sep: Option<IStr>,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Result<String> {
- let newline = newline.as_deref().unwrap_or("\n");
- let key_val_sep = key_val_sep.as_deref().unwrap_or(": ");
- manifest_json_ex(
- s,
- &value.0,
- &ManifestJsonOptions {
- padding: &indent,
- mtype: ManifestType::Std,
- newline,
- key_val_sep,
- #[cfg(feature = "exp-preserve-order")]
- preserve_order: preserve_order.unwrap_or(false),
- },
- )
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_manifest_yaml_doc(
- s: State,
- value: Any,
- indent_array_in_object: Option<bool>,
- quote_keys: Option<bool>,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Result<String> {
- manifest_yaml_ex(
- s,
- &value.0,
- &ManifestYamlOptions {
- padding: " ",
- arr_element_padding: if indent_array_in_object.unwrap_or(false) {
- " "
- } else {
- ""
- },
- quote_keys: quote_keys.unwrap_or(true),
- #[cfg(feature = "exp-preserve-order")]
- preserve_order: preserve_order.unwrap_or(false),
- },
- )
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_reverse(value: ArrValue) -> Result<ArrValue> {
- Ok(value.reversed())
-}
-
-#[jrsonnet_macros::builtin]
-const fn builtin_id(v: Any) -> Result<Any> {
- Ok(v)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_str_replace(str: String, from: IStr, to: IStr) -> Result<String> {
- Ok(str.replace(&from as &str, &to as &str))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> Result<VecVal> {
- use Either2::*;
- Ok(VecVal(Cc::new(match maxsplits {
- A(n) => str
- .splitn(n + 1, &c as &str)
- .map(|s| Val::Str(s.into()))
- .collect(),
- B(_) => str.split(&c as &str).map(|s| Val::Str(s.into())).collect(),
- })))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ascii_upper(str: IStr) -> Result<String> {
- Ok(str.to_ascii_uppercase())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ascii_lower(str: IStr) -> Result<String> {
- Ok(str.to_ascii_lowercase())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_member(s: State, arr: IndexableVal, x: Any) -> Result<bool> {
- match arr {
- IndexableVal::Str(str) => {
- let x: IStr = IStr::from_untyped(x.0, s)?;
- Ok(!x.is_empty() && str.contains(&*x))
- }
- IndexableVal::Arr(a) => {
- for item in a.iter(s.clone()) {
- let item = item?;
- if equals(s.clone(), &item, &x.0)? {
- return Ok(true);
- }
- }
- Ok(false)
- }
- }
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_count(s: State, arr: Vec<Any>, v: Any) -> Result<usize> {
- let mut count = 0;
- for item in &arr {
- if equals(s.clone(), &item.0, &v.0)? {
- count += 1;
- }
- }
- Ok(count)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_any(s: State, arr: ArrValue) -> Result<bool> {
- for v in arr.iter(s.clone()) {
- let v = bool::from_untyped(v?, s.clone())?;
- if v {
- return Ok(true);
- }
- }
- Ok(false)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_all(s: State, arr: ArrValue) -> Result<bool> {
- for v in arr.iter(s.clone()) {
- let v = bool::from_untyped(v?, s.clone())?;
- if !v {
- return Ok(false);
- }
- }
- Ok(true)
-}
crates/jrsonnet-evaluator/src/builtin/sort.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/builtin/sort.rs
+++ /dev/null
@@ -1,109 +0,0 @@
-use gcmodule::{Cc, Trace};
-
-use crate::{
- error::{Error, LocError, Result},
- throw,
- typed::Any,
- val::FuncVal,
- State, Val,
-};
-
-#[derive(Debug, Clone, thiserror::Error, Trace)]
-pub enum SortError {
- #[error("sort key should be string or number")]
- SortKeyShouldBeStringOrNumber,
- #[error("sort elements should have equal types")]
- SortElementsShouldHaveEqualType,
-}
-
-impl From<SortError> for LocError {
- fn from(s: SortError) -> Self {
- Self::new(Error::Sort(s))
- }
-}
-
-#[derive(Copy, Clone)]
-enum SortKeyType {
- Number,
- String,
- Unknown,
-}
-
-#[derive(PartialEq)]
-struct NonNaNf64(f64);
-impl PartialOrd for NonNaNf64 {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- self.0.partial_cmp(&other.0)
- }
-}
-impl Eq for NonNaNf64 {}
-impl Ord for NonNaNf64 {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- self.partial_cmp(other).expect("non nan")
- }
-}
-
-fn get_sort_type<T>(
- values: &mut Vec<T>,
- key_getter: impl Fn(&mut T) -> &mut Val,
-) -> Result<SortKeyType> {
- let mut sort_type = SortKeyType::Unknown;
- for i in values.iter_mut() {
- let i = key_getter(i);
- match (i, sort_type) {
- (Val::Str(_), SortKeyType::Unknown) => sort_type = SortKeyType::String,
- (Val::Num(_), SortKeyType::Unknown) => sort_type = SortKeyType::Number,
- (Val::Str(_), SortKeyType::String) | (Val::Num(_), SortKeyType::Number) => {}
- (Val::Str(_) | Val::Num(_), _) => {
- throw!(SortError::SortElementsShouldHaveEqualType)
- }
- _ => throw!(SortError::SortKeyShouldBeStringOrNumber),
- }
- }
- Ok(sort_type)
-}
-
-pub fn sort(s: State, values: Cc<Vec<Val>>, key_getter: Option<&FuncVal>) -> Result<Cc<Vec<Val>>> {
- if values.len() <= 1 {
- return Ok(values);
- }
- if let Some(key_getter) = key_getter {
- // Slow path, user provided key getter
- let mut vk = Vec::with_capacity(values.len());
- for value in values.iter() {
- vk.push((
- value.clone(),
- key_getter.evaluate_simple(s.clone(), &[Any(value.clone())].as_slice())?,
- ));
- }
- let sort_type = get_sort_type(&mut vk, |v| &mut v.1)?;
- match sort_type {
- SortKeyType::Number => vk.sort_by_key(|v| match v.1 {
- Val::Num(n) => NonNaNf64(n),
- _ => unreachable!(),
- }),
- SortKeyType::String => vk.sort_by_key(|v| match &v.1 {
- Val::Str(s) => s.clone(),
- _ => unreachable!(),
- }),
- SortKeyType::Unknown => unreachable!(),
- };
- Ok(Cc::new(vk.into_iter().map(|v| v.0).collect()))
- } else {
- // Fast path, identity key getter
- let mut values = (*values).clone();
- let sort_type = get_sort_type(&mut values, |k| k)?;
- match sort_type {
- SortKeyType::Number => values.sort_unstable_by_key(|v| match v {
- Val::Num(n) => NonNaNf64(*n),
- _ => unreachable!(),
- }),
- SortKeyType::String => values.sort_unstable_by_key(|v| match v {
- Val::Str(s) => s.clone(),
- _ => unreachable!(),
- }),
- SortKeyType::Unknown => unreachable!(),
- };
- Ok(Cc::new(values))
- }
-}
crates/jrsonnet-evaluator/src/builtin/stdlib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/builtin/stdlib.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use std::path::PathBuf;
-
-use jrsonnet_parser::{LocExpr, ParserSettings};
-
-thread_local! {
- /// To avoid parsing again when issued from the same thread
- #[allow(unreachable_code)]
- static PARSED_STDLIB: LocExpr = {
- #[cfg(feature = "serialized-stdlib")]
- {
- // Should not panic, stdlib.bincode is generated in build.rs
- return bincode::deserialize(include_bytes!(concat!(env!("OUT_DIR"), "/stdlib.bincode")))
- .unwrap();
- }
-
- jrsonnet_parser::parse(
- jrsonnet_stdlib::STDLIB_STR,
- &ParserSettings {
- file_name: PathBuf::from("std.jsonnet").into(),
- },
- )
- .unwrap()
- }
-}
-
-pub fn get_parsed_stdlib() -> LocExpr {
- PARSED_STDLIB.with(Clone::clone)
-}
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -11,7 +11,7 @@
use thiserror::Error;
use crate::{
- builtin::{format::FormatError, sort::SortError},
+ stdlib::{format::FormatError, sort::SortError},
typed::TypeLocError,
};
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -7,14 +7,14 @@
use jrsonnet_types::ValType;
use crate::{
- builtin::{std_slice, BUILTINS},
error::Error::*,
evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},
- function::CallLocation,
+ function::{CallLocation, FuncDesc, FuncVal},
gc::TraceBox,
+ stdlib::{std_slice, BUILTINS},
throw,
typed::Typed,
- val::{ArrValue, FuncDesc, FuncVal, LazyValValue},
+ val::{ArrValue, LazyValValue},
Bindable, Context, ContextCreator, FutureWrapper, GcHashMap, LazyBinding, LazyVal, ObjValue,
ObjValueBuilder, ObjectAssertion, Result, State, Val,
};
@@ -676,6 +676,7 @@
.ok_or_else(|| IntrinsicNotFound(name.clone()))?,
)),
IntrinsicThisFile => return Err(MagicThisFileUsed.into()),
+ IntrinsicId => Val::Func(FuncVal::identity()),
AssertExpr(assert, returned) => {
evaluate_assert(s.clone(), ctx.clone(), assert)?;
evaluate(s, ctx, returned)?
crates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -3,7 +3,7 @@
use jrsonnet_parser::{BinaryOpType, LocExpr, UnaryOpType};
use crate::{
- builtin::std_format, error::Error::*, evaluate, throw, typed::Typed, val::equals, Context,
+ error::Error::*, evaluate, stdlib::std_format, throw, typed::Typed, val::equals, Context,
Result, State, Val,
};
crates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function.rs
+++ /dev/null
@@ -1,549 +0,0 @@
-use std::{borrow::Cow, collections::HashMap};
-
-use gcmodule::Trace;
-use jrsonnet_interner::IStr;
-pub use jrsonnet_macros::builtin;
-use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};
-
-use crate::{
- error::Error::*, evaluate, evaluate_named, gc::TraceBox, throw, typed::Typed,
- val::LazyValValue, Context, FutureWrapper, GcHashMap, LazyVal, Result, State, Val,
-};
-
-#[derive(Clone, Copy)]
-pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);
-impl<'l> CallLocation<'l> {
- pub const fn new(loc: &'l ExprLocation) -> Self {
- Self(Some(loc))
- }
-}
-impl CallLocation<'static> {
- pub const fn native() -> Self {
- Self(None)
- }
-}
-
-#[derive(Trace)]
-struct EvaluateLazyVal {
- ctx: Context,
- expr: LocExpr,
-}
-impl LazyValValue for EvaluateLazyVal {
- fn get(self: Box<Self>, s: State) -> Result<Val> {
- evaluate(s, self.ctx, &self.expr)
- }
-}
-
-#[derive(Trace)]
-struct EvaluateNamedLazyVal {
- ctx: FutureWrapper<Context>,
- name: IStr,
- value: LocExpr,
-}
-impl LazyValValue for EvaluateNamedLazyVal {
- fn get(self: Box<Self>, s: State) -> Result<Val> {
- evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)
- }
-}
-pub trait ArgLike {
- fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal>;
-}
-impl ArgLike for &LocExpr {
- fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {
- Ok(if tailstrict {
- LazyVal::new_resolved(evaluate(s, ctx, self)?)
- } else {
- LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
- ctx,
- expr: (*self).clone(),
- })))
- })
- }
-}
-impl<T> ArgLike for T
-where
- T: Typed + Clone,
-{
- fn evaluate_arg(&self, s: State, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {
- let val = T::into_untyped(self.clone(), s)?;
- Ok(LazyVal::new_resolved(val))
- }
-}
-pub enum TlaArg {
- String(IStr),
- Code(LocExpr),
- Val(Val),
-}
-impl ArgLike for TlaArg {
- fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {
- match self {
- TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),
- TlaArg::Code(code) => Ok(if tailstrict {
- LazyVal::new_resolved(evaluate(s, ctx, code)?)
- } else {
- LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
- ctx,
- expr: code.clone(),
- })))
- }),
- TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),
- }
- }
-}
-
-pub trait ArgsLike {
- fn unnamed_len(&self) -> usize;
- fn unnamed_iter(
- &self,
- s: State,
- ctx: Context,
- tailstrict: bool,
- handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
- ) -> Result<()>;
- fn named_iter(
- &self,
- s: State,
- ctx: Context,
- tailstrict: bool,
- handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
- ) -> Result<()>;
- fn named_names(&self, handler: &mut dyn FnMut(&IStr));
-}
-
-impl ArgsLike for ArgsDesc {
- fn unnamed_len(&self) -> usize {
- self.unnamed.len()
- }
-
- fn unnamed_iter(
- &self,
- s: State,
- ctx: Context,
- tailstrict: bool,
- handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
- ) -> Result<()> {
- for (id, arg) in self.unnamed.iter().enumerate() {
- handler(
- id,
- if tailstrict {
- LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)
- } else {
- LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
- ctx: ctx.clone(),
- expr: arg.clone(),
- })))
- },
- )?;
- }
- Ok(())
- }
-
- fn named_iter(
- &self,
- s: State,
- ctx: Context,
- tailstrict: bool,
- handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
- ) -> Result<()> {
- for (name, arg) in &self.named {
- handler(
- name,
- if tailstrict {
- LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)
- } else {
- LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
- ctx: ctx.clone(),
- expr: arg.clone(),
- })))
- },
- )?;
- }
- Ok(())
- }
-
- fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
- for (name, _) in &self.named {
- handler(name);
- }
- }
-}
-
-impl ArgsLike for [(); 0] {
- fn unnamed_len(&self) -> usize {
- 0
- }
-
- fn unnamed_iter(
- &self,
- _s: State,
- _ctx: Context,
- _tailstrict: bool,
- _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
- ) -> Result<()> {
- Ok(())
- }
-
- fn named_iter(
- &self,
- _s: State,
- _ctx: Context,
- _tailstrict: bool,
- _handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
- ) -> Result<()> {
- Ok(())
- }
-
- fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}
-}
-
-impl<A: ArgLike> ArgsLike for [(IStr, A)] {
- fn unnamed_len(&self) -> usize {
- 0
- }
-
- fn unnamed_iter(
- &self,
- _s: State,
- _ctx: Context,
- _tailstrict: bool,
- _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
- ) -> Result<()> {
- Ok(())
- }
-
- fn named_iter(
- &self,
- s: State,
- ctx: Context,
- tailstrict: bool,
- handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
- ) -> Result<()> {
- for (name, val) in self.iter() {
- handler(name, val.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;
- }
- Ok(())
- }
-
- fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
- for (name, _) in self.iter() {
- handler(name);
- }
- }
-}
-
-impl<A: ArgLike, S> ArgsLike for HashMap<IStr, A, S> {
- fn unnamed_len(&self) -> usize {
- 0
- }
-
- fn unnamed_iter(
- &self,
- _s: State,
- _ctx: Context,
- _tailstrict: bool,
- _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
- ) -> Result<()> {
- Ok(())
- }
-
- fn named_iter(
- &self,
- s: State,
- ctx: Context,
- tailstrict: bool,
- handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
- ) -> Result<()> {
- for (name, value) in self.iter() {
- handler(
- name,
- value.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?,
- )?;
- }
- Ok(())
- }
-
- fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
- for (name, _) in self.iter() {
- handler(name);
- }
- }
-}
-
-impl<A: ArgLike> ArgsLike for [A] {
- fn unnamed_len(&self) -> usize {
- self.len()
- }
-
- fn unnamed_iter(
- &self,
- s: State,
- ctx: Context,
- tailstrict: bool,
- handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
- ) -> Result<()> {
- for (i, arg) in self.iter().enumerate() {
- handler(i, arg.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;
- }
- Ok(())
- }
-
- fn named_iter(
- &self,
- _s: State,
- _ctx: Context,
- _tailstrict: bool,
- _handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
- ) -> Result<()> {
- Ok(())
- }
-
- fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}
-}
-impl<A: ArgLike> ArgsLike for &[A] {
- fn unnamed_len(&self) -> usize {
- (*self).unnamed_len()
- }
-
- fn unnamed_iter(
- &self,
- s: State,
- ctx: Context,
- tailstrict: bool,
- handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
- ) -> Result<()> {
- (*self).unnamed_iter(s, ctx, tailstrict, handler)
- }
-
- fn named_iter(
- &self,
- s: State,
- ctx: Context,
- tailstrict: bool,
- handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
- ) -> Result<()> {
- (*self).named_iter(s, ctx, tailstrict, handler)
- }
-
- fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
- (*self).named_names(handler);
- }
-}
-
-/// Creates correct [context](Context) for function body evaluation returning error on invalid call.
-///
-/// ## Parameters
-/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)
-/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)
-/// * `params`: function parameters' definition
-/// * `args`: passed function arguments
-/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily
-pub fn parse_function_call(
- s: State,
- ctx: Context,
- body_ctx: Context,
- params: &ParamsDesc,
- args: &dyn ArgsLike,
- tailstrict: bool,
-) -> Result<Context> {
- let mut passed_args = GcHashMap::with_capacity(params.len());
- if args.unnamed_len() > params.len() {
- throw!(TooManyArgsFunctionHas(params.len()))
- }
-
- let mut filled_args = 0;
-
- args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {
- let name = params[id].0.clone();
- passed_args.insert(name, arg);
- filled_args += 1;
- Ok(())
- })?;
-
- args.named_iter(s, ctx, tailstrict, &mut |name, value| {
- // FIXME: O(n) for arg existence check
- if !params.iter().any(|p| &p.0 == name) {
- throw!(UnknownFunctionParameter((name as &str).to_owned()));
- }
- if passed_args.insert(name.clone(), value).is_some() {
- throw!(BindingParameterASecondTime(name.clone()));
- }
- filled_args += 1;
- Ok(())
- })?;
-
- if filled_args < params.len() {
- // Some args are unset, but maybe we have defaults for them
- // Default values should be created in newly created context
- let fctx = Context::new_future();
- let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);
-
- for param in params.iter().filter(|p| p.1.is_some()) {
- if passed_args.contains_key(¶m.0.clone()) {
- continue;
- }
-
- defaults.insert(
- param.0.clone(),
- LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {
- ctx: fctx.clone(),
- name: param.0.clone(),
- value: param.1.clone().expect("default exists"),
- }))),
- );
- filled_args += 1;
- }
-
- // Some args still wasn't filled
- if filled_args != params.len() {
- for param in params.iter().skip(args.unnamed_len()) {
- let mut found = false;
- args.named_names(&mut |name| {
- if name == ¶m.0 {
- found = true;
- }
- });
- if !found {
- throw!(FunctionParameterNotBoundInCall(param.0.clone()));
- }
- }
- unreachable!();
- }
-
- Ok(body_ctx
- .extend(passed_args, None, None, None)
- .extend_bound(defaults)
- .into_future(fctx))
- } else {
- let body_ctx = body_ctx.extend(passed_args, None, None, None);
- Ok(body_ctx)
- }
-}
-
-type BuiltinParamName = Cow<'static, str>;
-
-#[derive(Clone, Trace)]
-pub struct BuiltinParam {
- pub name: BuiltinParamName,
- pub has_default: bool,
-}
-
-/// Do not implement it directly, instead use #[builtin] macro
-pub trait Builtin: Trace {
- fn name(&self) -> &str;
- fn params(&self) -> &[BuiltinParam];
- fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;
-}
-
-pub trait StaticBuiltin: Builtin + Send + Sync
-where
- Self: 'static,
-{
- // In impl, to make it object safe:
- // const INST: &'static Self;
-}
-
-/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead
-///
-/// ## Parameters
-/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)
-/// * `params`: function parameters' definition
-/// * `args`: passed function arguments
-/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily
-pub fn parse_builtin_call(
- s: State,
- ctx: Context,
- params: &[BuiltinParam],
- args: &dyn ArgsLike,
- tailstrict: bool,
-) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {
- let mut passed_args = GcHashMap::with_capacity(params.len());
- if args.unnamed_len() > params.len() {
- throw!(TooManyArgsFunctionHas(params.len()))
- }
-
- let mut filled_args = 0;
-
- args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {
- let name = params[id].name.clone();
- passed_args.insert(name, arg);
- filled_args += 1;
- Ok(())
- })?;
-
- args.named_iter(s, ctx, tailstrict, &mut |name, arg| {
- // FIXME: O(n) for arg existence check
- let p = params
- .iter()
- .find(|p| p.name == name as &str)
- .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;
- if passed_args.insert(p.name.clone(), arg).is_some() {
- throw!(BindingParameterASecondTime(name.clone()));
- }
- filled_args += 1;
- Ok(())
- })?;
-
- if filled_args < params.len() {
- for param in params.iter().filter(|p| p.has_default) {
- if passed_args.contains_key(¶m.name) {
- continue;
- }
- filled_args += 1;
- }
-
- // Some args still wasn't filled
- if filled_args != params.len() {
- for param in params.iter().skip(args.unnamed_len()) {
- let mut found = false;
- args.named_names(&mut |name| {
- if name as &str == ¶m.name as &str {
- found = true;
- }
- });
- if !found {
- throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));
- }
- }
- unreachable!();
- }
- }
- Ok(passed_args)
-}
-
-/// Creates Context, which has all argument default values applied
-/// and with unbound values causing error to be returned
-pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context {
- #[derive(Trace)]
- struct DependsOnUnbound(IStr);
- impl LazyValValue for DependsOnUnbound {
- fn get(self: Box<Self>, _: State) -> Result<Val> {
- Err(FunctionParameterNotBoundInCall(self.0.clone()).into())
- }
- }
-
- let fctx = Context::new_future();
-
- let mut bindings = GcHashMap::new();
-
- for param in params.iter() {
- if let Some(v) = ¶m.1 {
- bindings.insert(
- param.0.clone(),
- LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {
- ctx: fctx.clone(),
- name: param.0.clone(),
- value: v.clone(),
- }))),
- );
- } else {
- bindings.insert(
- param.0.clone(),
- LazyVal::new(TraceBox(Box::new(DependsOnUnbound(param.0.clone())))),
- );
- }
- }
-
- body_ctx
- .extend(bindings, None, None, None)
- .into_future(fctx)
-}
crates/jrsonnet-evaluator/src/function/arglike.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/function/arglike.rs
@@ -0,0 +1,304 @@
+use std::collections::HashMap;
+
+use gcmodule::Trace;
+use jrsonnet_interner::IStr;
+use jrsonnet_parser::{ArgsDesc, LocExpr};
+
+use crate::{
+ error::Result, evaluate, gc::TraceBox, typed::Typed, val::LazyValValue, Context, LazyVal,
+ State, Val,
+};
+
+#[derive(Trace)]
+struct EvaluateLazyVal {
+ ctx: Context,
+ expr: LocExpr,
+}
+impl LazyValValue for EvaluateLazyVal {
+ fn get(self: Box<Self>, s: State) -> Result<Val> {
+ evaluate(s, self.ctx, &self.expr)
+ }
+}
+
+pub trait ArgLike {
+ fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal>;
+}
+
+impl ArgLike for &LocExpr {
+ fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {
+ Ok(if tailstrict {
+ LazyVal::new_resolved(evaluate(s, ctx, self)?)
+ } else {
+ LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
+ ctx,
+ expr: (*self).clone(),
+ })))
+ })
+ }
+}
+
+impl<T> ArgLike for T
+where
+ T: Typed + Clone,
+{
+ fn evaluate_arg(&self, s: State, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {
+ let val = T::into_untyped(self.clone(), s)?;
+ Ok(LazyVal::new_resolved(val))
+ }
+}
+
+pub enum TlaArg {
+ String(IStr),
+ Code(LocExpr),
+ Val(Val),
+}
+impl ArgLike for TlaArg {
+ fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {
+ match self {
+ TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),
+ TlaArg::Code(code) => Ok(if tailstrict {
+ LazyVal::new_resolved(evaluate(s, ctx, code)?)
+ } else {
+ LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
+ ctx,
+ expr: code.clone(),
+ })))
+ }),
+ TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),
+ }
+ }
+}
+
+mod sealed {
+ /// Implemented for `ArgsLike`, where only unnamed arguments present
+ pub trait Unnamed {}
+ /// Implemented for `ArgsLike`, where only named arguments present
+ pub trait Named {}
+}
+
+pub trait ArgsLike {
+ fn unnamed_len(&self) -> usize;
+ fn unnamed_iter(
+ &self,
+ s: State,
+ ctx: Context,
+ tailstrict: bool,
+ handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+ ) -> Result<()>;
+ fn named_iter(
+ &self,
+ s: State,
+ ctx: Context,
+ tailstrict: bool,
+ handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+ ) -> Result<()>;
+ fn named_names(&self, handler: &mut dyn FnMut(&IStr));
+}
+
+impl ArgsLike for ArgsDesc {
+ fn unnamed_len(&self) -> usize {
+ self.unnamed.len()
+ }
+
+ fn unnamed_iter(
+ &self,
+ s: State,
+ ctx: Context,
+ tailstrict: bool,
+ handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ for (id, arg) in self.unnamed.iter().enumerate() {
+ handler(
+ id,
+ if tailstrict {
+ LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)
+ } else {
+ LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
+ ctx: ctx.clone(),
+ expr: arg.clone(),
+ })))
+ },
+ )?;
+ }
+ Ok(())
+ }
+
+ fn named_iter(
+ &self,
+ s: State,
+ ctx: Context,
+ tailstrict: bool,
+ handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ for (name, arg) in &self.named {
+ handler(
+ name,
+ if tailstrict {
+ LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)
+ } else {
+ LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
+ ctx: ctx.clone(),
+ expr: arg.clone(),
+ })))
+ },
+ )?;
+ }
+ Ok(())
+ }
+
+ fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
+ for (name, _) in &self.named {
+ handler(name);
+ }
+ }
+}
+
+impl<A: ArgLike, S> sealed::Named for HashMap<IStr, A, S> {}
+impl<A: ArgLike, S> ArgsLike for HashMap<IStr, A, S> {
+ fn unnamed_len(&self) -> usize {
+ 0
+ }
+
+ fn unnamed_iter(
+ &self,
+ _s: State,
+ _ctx: Context,
+ _tailstrict: bool,
+ _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ Ok(())
+ }
+
+ fn named_iter(
+ &self,
+ s: State,
+ ctx: Context,
+ tailstrict: bool,
+ handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ for (name, value) in self.iter() {
+ handler(
+ name,
+ value.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?,
+ )?;
+ }
+ Ok(())
+ }
+
+ fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
+ for (name, _) in self.iter() {
+ handler(name);
+ }
+ }
+}
+
+macro_rules! impl_args_like {
+ ($count:expr; $($gen:ident)*) => {
+ impl<$($gen: ArgLike,)*> sealed::Unnamed for ($($gen,)*) {}
+ impl<$($gen: ArgLike,)*> ArgsLike for ($($gen,)*) {
+ fn unnamed_len(&self) -> usize {
+ $count
+ }
+ #[allow(non_snake_case, unused_assignments)]
+ fn unnamed_iter(
+ &self,
+ s: State,
+ ctx: Context,
+ tailstrict: bool,
+ handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ let mut i = 0usize;
+ let ($($gen,)*) = self;
+ $(
+ handler(i, $gen.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;
+ i+=1;
+ )*
+ Ok(())
+ }
+ fn named_iter(
+ &self,
+ _s: State,
+ _ctx: Context,
+ _tailstrict: bool,
+ _handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ Ok(())
+ }
+ fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}
+ }
+ impl<$($gen: ArgLike,)*> sealed::Named for ($((IStr, $gen),)*) {}
+ impl<$($gen: ArgLike,)*> ArgsLike for ($((IStr, $gen),)*) {
+ fn unnamed_len(&self) -> usize {
+ 0
+ }
+ fn unnamed_iter(
+ &self,
+ _s: State,
+ _ctx: Context,
+ _tailstrict: bool,
+ _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ Ok(())
+ }
+ #[allow(non_snake_case)]
+ fn named_iter(
+ &self,
+ s: State,
+ ctx: Context,
+ tailstrict: bool,
+ handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ let ($($gen,)*) = self;
+ $(
+ handler(&$gen.0, $gen.1.evaluate_arg(s.clone(), ctx.clone(), tailstrict)?)?;
+ )*
+ Ok(())
+ }
+ #[allow(non_snake_case)]
+ fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {
+ let ($($gen,)*) = self;
+ $(
+ handler(&$gen.0);
+ )*
+ }
+ }
+ };
+ ($count:expr; $($cur:ident)* @ $c:ident $($rest:ident)*) => {
+ impl_args_like!($count; $($cur)*);
+ impl_args_like!($count + 1usize; $($cur)* $c @ $($rest)*);
+ };
+ ($count:expr; $($cur:ident)* @) => {
+ impl_args_like!($count; $($cur)*);
+ }
+}
+impl_args_like! {
+ 0usize; A @ B C D E F G H I J K L
+}
+
+impl ArgsLike for () {
+ fn unnamed_len(&self) -> usize {
+ 0
+ }
+
+ fn unnamed_iter(
+ &self,
+ _s: State,
+ _ctx: Context,
+ _tailstrict: bool,
+ _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ Ok(())
+ }
+
+ fn named_iter(
+ &self,
+ _s: State,
+ _ctx: Context,
+ _tailstrict: bool,
+ _handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ Ok(())
+ }
+
+ fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}
+}
crates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -0,0 +1,66 @@
+use std::{borrow::Cow, path::Path, rc::Rc};
+
+use gcmodule::Trace;
+
+use super::{arglike::ArgsLike, parse::parse_builtin_call, CallLocation};
+use crate::{error::Result, gc::TraceBox, Context, State, Val};
+
+pub type BuiltinParamName = Cow<'static, str>;
+
+#[derive(Clone, Trace)]
+pub struct BuiltinParam {
+ pub name: BuiltinParamName,
+ pub has_default: bool,
+}
+
+/// Do not implement it directly, instead use #[builtin] macro
+pub trait Builtin: Trace {
+ fn name(&self) -> &str;
+ fn params(&self) -> &[BuiltinParam];
+ fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;
+}
+
+pub trait StaticBuiltin: Builtin + Send + Sync
+where
+ Self: 'static,
+{
+ // In impl, to make it object safe:
+ // const INST: &'static Self;
+}
+
+#[derive(Trace)]
+pub struct NativeCallback {
+ pub(crate) params: Vec<BuiltinParam>,
+ handler: TraceBox<dyn NativeCallbackHandler>,
+}
+impl NativeCallback {
+ #[deprecated = "prefer using builtins directly, use this interface only for bindings"]
+ pub fn new(params: Vec<BuiltinParam>, handler: TraceBox<dyn NativeCallbackHandler>) -> Self {
+ Self { params, handler }
+ }
+}
+
+impl Builtin for NativeCallback {
+ fn name(&self) -> &str {
+ // TODO: standard natives gets their names from definition
+ // But builitins should already have them
+ "<native>"
+ }
+
+ fn params(&self) -> &[BuiltinParam] {
+ &self.params
+ }
+
+ fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
+ let args = parse_builtin_call(s.clone(), ctx, &self.params, args, true)?;
+ let mut out_args = Vec::with_capacity(self.params.len());
+ for p in &self.params {
+ out_args.push(args[&p.name].evaluate(s.clone())?);
+ }
+ self.handler.call(s, loc.0.map(|l| l.0.clone()), &out_args)
+ }
+}
+
+pub trait NativeCallbackHandler: Trace {
+ fn call(&self, s: State, from: Option<Rc<Path>>, args: &[Val]) -> Result<Val>;
+}
crates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -0,0 +1,168 @@
+use std::fmt::Debug;
+
+pub use arglike::{ArgLike, ArgsLike, TlaArg};
+use gcmodule::{Cc, Trace};
+use jrsonnet_interner::IStr;
+pub use jrsonnet_macros::builtin;
+use jrsonnet_parser::{ExprLocation, LocExpr, ParamsDesc};
+
+use self::{
+ builtin::{Builtin, StaticBuiltin},
+ native::NativeDesc,
+ parse::{parse_default_function_call, parse_function_call},
+};
+use crate::{evaluate, gc::TraceBox, typed::Any, Context, Result, State, Val};
+
+pub mod arglike;
+pub mod builtin;
+pub mod native;
+pub mod parse;
+
+#[derive(Clone, Copy)]
+pub struct CallLocation<'l>(pub Option<&'l ExprLocation>);
+impl<'l> CallLocation<'l> {
+ pub const fn new(loc: &'l ExprLocation) -> Self {
+ Self(Some(loc))
+ }
+}
+impl CallLocation<'static> {
+ pub const fn native() -> Self {
+ Self(None)
+ }
+}
+
+/// Function implemented in jsonnet
+#[derive(Debug, PartialEq, Trace)]
+pub struct FuncDesc {
+ /// In expressions like
+ /// ```jsonnet
+ /// local a = function() ...
+ /// local a() ...
+ /// { a: function() ... }
+ /// { a() = ... }
+ /// ```
+ ///
+ /// Deducted to `a`, unspecified otherwise
+ pub name: IStr,
+ /// Context, in which this function was evaluated
+ ///
+ /// I.e in
+ /// ```jsonnet
+ /// local a = 2;
+ /// function() ...
+ /// ```
+ /// context will contain `a`
+ pub ctx: Context,
+
+ pub params: ParamsDesc,
+ pub body: LocExpr,
+}
+impl FuncDesc {
+ /// Create body context, but fill arguments without defaults with lazy error
+ pub fn default_body_context(&self) -> Context {
+ parse_default_function_call(self.ctx.clone(), &self.params)
+ }
+
+ /// Create context, with which body code will run
+ pub fn call_body_context(
+ &self,
+ s: State,
+ call_ctx: Context,
+ args: &dyn ArgsLike,
+ tailstrict: bool,
+ ) -> Result<Context> {
+ parse_function_call(
+ s,
+ call_ctx,
+ self.ctx.clone(),
+ &self.params,
+ args,
+ tailstrict,
+ )
+ }
+}
+
+/// Any possible function value, including plain functions and user-provided builtins
+#[allow(clippy::module_name_repetitions)]
+#[derive(Trace, Clone)]
+pub enum FuncVal {
+ /// std.id
+ Id,
+ /// Plain function implemented in jsonnet
+ Normal(Cc<FuncDesc>),
+ /// Standard library function
+ StaticBuiltin(#[skip_trace] &'static dyn StaticBuiltin),
+ /// User-provided function
+ Builtin(Cc<TraceBox<dyn Builtin>>),
+}
+
+impl Debug for FuncVal {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Id => f.debug_tuple("Id").finish(),
+ Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),
+ Self::StaticBuiltin(arg0) => {
+ f.debug_tuple("StaticBuiltin").field(&arg0.name()).finish()
+ }
+ Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),
+ }
+ }
+}
+
+impl FuncVal {
+ pub fn into_native<D: NativeDesc>(self) -> D::Value {
+ D::into_native(self)
+ }
+ pub fn params_len(&self) -> usize {
+ match self {
+ Self::Id => 1,
+ Self::Normal(n) => n.params.iter().filter(|p| p.1.is_none()).count(),
+ Self::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default).count(),
+ Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default).count(),
+ }
+ }
+ pub fn name(&self) -> IStr {
+ match self {
+ Self::Id => "id".into(),
+ Self::Normal(normal) => normal.name.clone(),
+ Self::StaticBuiltin(builtin) => builtin.name().into(),
+ Self::Builtin(builtin) => builtin.name().into(),
+ }
+ }
+ pub fn evaluate(
+ &self,
+ s: State,
+ call_ctx: Context,
+ loc: CallLocation,
+ args: &dyn ArgsLike,
+ tailstrict: bool,
+ ) -> Result<Val> {
+ match self {
+ Self::Id => {
+ #[builtin]
+ fn builtin_id(v: Any) -> Result<Any> {
+ Ok(v)
+ }
+ static ID: &'static builtin_id = &builtin_id {};
+
+ ID.call(s, call_ctx, loc, args)
+ }
+ Self::Normal(func) => {
+ let body_ctx = func.call_body_context(s.clone(), call_ctx, args, tailstrict)?;
+ evaluate(s, body_ctx, &func.body)
+ }
+ Self::StaticBuiltin(b) => b.call(s, call_ctx, loc, args),
+ Self::Builtin(b) => b.call(s, call_ctx, loc, args),
+ }
+ }
+ pub fn evaluate_simple(&self, s: State, args: &dyn ArgsLike) -> Result<Val> {
+ self.evaluate(s, Context::default(), CallLocation::native(), args, true)
+ }
+
+ pub fn is_identity(&self) -> bool {
+ matches!(self, Self::Id)
+ }
+ pub fn identity() -> Self {
+ Self::Id
+ }
+}
crates/jrsonnet-evaluator/src/function/native.rsdiffbeforeafterboth1use super::{arglike::ArgLike, CallLocation, FuncVal};2use crate::{error::Result, typed::Typed, State};34pub trait NativeDesc {5 type Value;6 fn into_native(val: FuncVal) -> Self::Value;7}8macro_rules! impl_native_desc {9 ($($gen:ident)*) => {10 impl<$($gen,)* O> NativeDesc for (($($gen,)*), O)11 where12 $($gen: ArgLike,)*13 O: Typed,14 {15 type Value = Box<dyn Fn(State, $($gen,)*) -> Result<O>>;1617 #[allow(non_snake_case)]18 fn into_native(val: FuncVal) -> Self::Value {19 Box::new(move |s: State, $($gen),*| {20 let val = val.evaluate(21 s.clone(),22 s.create_default_context(),23 CallLocation::native(),24 &($($gen,)*),25 true26 )?;27 O::from_untyped(val, s.clone())28 })29 }30 }31 };32 ($($cur:ident)* @ $c:ident $($rest:ident)*) => {33 impl_native_desc!($($cur)*);34 impl_native_desc!($($cur)* $c @ $($rest)*);35 };36 ($($cur:ident)* @) => {37 impl_native_desc!($($cur)*);38 }39}4041impl_native_desc! {42 @ A B C D E F G H I J K L43}crates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -0,0 +1,225 @@
+use gcmodule::Trace;
+use jrsonnet_interner::IStr;
+use jrsonnet_parser::{LocExpr, ParamsDesc};
+
+use super::{
+ arglike::ArgsLike,
+ builtin::{BuiltinParam, BuiltinParamName},
+};
+use crate::{
+ error::{Error::*, Result},
+ evaluate_named,
+ gc::{GcHashMap, TraceBox},
+ throw,
+ val::LazyValValue,
+ Context, FutureWrapper, LazyVal, State, Val,
+};
+
+#[derive(Trace)]
+struct EvaluateNamedLazyVal {
+ ctx: FutureWrapper<Context>,
+ name: IStr,
+ value: LocExpr,
+}
+
+impl LazyValValue for EvaluateNamedLazyVal {
+ fn get(self: Box<Self>, s: State) -> Result<Val> {
+ evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)
+ }
+}
+
+/// Creates correct [context](Context) for function body evaluation returning error on invalid call.
+///
+/// ## Parameters
+/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)
+/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)
+/// * `params`: function parameters' definition
+/// * `args`: passed function arguments
+/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily
+pub fn parse_function_call(
+ s: State,
+ ctx: Context,
+ body_ctx: Context,
+ params: &ParamsDesc,
+ args: &dyn ArgsLike,
+ tailstrict: bool,
+) -> Result<Context> {
+ let mut passed_args = GcHashMap::with_capacity(params.len());
+ if args.unnamed_len() > params.len() {
+ throw!(TooManyArgsFunctionHas(params.len()))
+ }
+
+ let mut filled_args = 0;
+
+ args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {
+ let name = params[id].0.clone();
+ passed_args.insert(name, arg);
+ filled_args += 1;
+ Ok(())
+ })?;
+
+ args.named_iter(s, ctx, tailstrict, &mut |name, value| {
+ // FIXME: O(n) for arg existence check
+ if !params.iter().any(|p| &p.0 == name) {
+ throw!(UnknownFunctionParameter((name as &str).to_owned()));
+ }
+ if passed_args.insert(name.clone(), value).is_some() {
+ throw!(BindingParameterASecondTime(name.clone()));
+ }
+ filled_args += 1;
+ Ok(())
+ })?;
+
+ if filled_args < params.len() {
+ // Some args are unset, but maybe we have defaults for them
+ // Default values should be created in newly created context
+ let fctx = Context::new_future();
+ let mut defaults = GcHashMap::with_capacity(params.len() - filled_args);
+
+ for param in params.iter().filter(|p| p.1.is_some()) {
+ if passed_args.contains_key(¶m.0.clone()) {
+ continue;
+ }
+
+ defaults.insert(
+ param.0.clone(),
+ LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {
+ ctx: fctx.clone(),
+ name: param.0.clone(),
+ value: param.1.clone().expect("default exists"),
+ }))),
+ );
+ filled_args += 1;
+ }
+
+ // Some args still wasn't filled
+ if filled_args != params.len() {
+ for param in params.iter().skip(args.unnamed_len()) {
+ let mut found = false;
+ args.named_names(&mut |name| {
+ if name == ¶m.0 {
+ found = true;
+ }
+ });
+ if !found {
+ throw!(FunctionParameterNotBoundInCall(param.0.clone()));
+ }
+ }
+ unreachable!();
+ }
+
+ Ok(body_ctx
+ .extend(passed_args, None, None, None)
+ .extend_bound(defaults)
+ .into_future(fctx))
+ } else {
+ let body_ctx = body_ctx.extend(passed_args, None, None, None);
+ Ok(body_ctx)
+ }
+}
+
+/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead
+///
+/// ## Parameters
+/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)
+/// * `params`: function parameters' definition
+/// * `args`: passed function arguments
+/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily
+pub fn parse_builtin_call(
+ s: State,
+ ctx: Context,
+ params: &[BuiltinParam],
+ args: &dyn ArgsLike,
+ tailstrict: bool,
+) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {
+ let mut passed_args = GcHashMap::with_capacity(params.len());
+ if args.unnamed_len() > params.len() {
+ throw!(TooManyArgsFunctionHas(params.len()))
+ }
+
+ let mut filled_args = 0;
+
+ args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {
+ let name = params[id].name.clone();
+ passed_args.insert(name, arg);
+ filled_args += 1;
+ Ok(())
+ })?;
+
+ args.named_iter(s, ctx, tailstrict, &mut |name, arg| {
+ // FIXME: O(n) for arg existence check
+ let p = params
+ .iter()
+ .find(|p| p.name == name as &str)
+ .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;
+ if passed_args.insert(p.name.clone(), arg).is_some() {
+ throw!(BindingParameterASecondTime(name.clone()));
+ }
+ filled_args += 1;
+ Ok(())
+ })?;
+
+ if filled_args < params.len() {
+ for param in params.iter().filter(|p| p.has_default) {
+ if passed_args.contains_key(¶m.name) {
+ continue;
+ }
+ filled_args += 1;
+ }
+
+ // Some args still wasn't filled
+ if filled_args != params.len() {
+ for param in params.iter().skip(args.unnamed_len()) {
+ let mut found = false;
+ args.named_names(&mut |name| {
+ if name as &str == ¶m.name as &str {
+ found = true;
+ }
+ });
+ if !found {
+ throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));
+ }
+ }
+ unreachable!();
+ }
+ }
+ Ok(passed_args)
+}
+
+/// Creates Context, which has all argument default values applied
+/// and with unbound values causing error to be returned
+pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context {
+ #[derive(Trace)]
+ struct DependsOnUnbound(IStr);
+ impl LazyValValue for DependsOnUnbound {
+ fn get(self: Box<Self>, _: State) -> Result<Val> {
+ Err(FunctionParameterNotBoundInCall(self.0.clone()).into())
+ }
+ }
+
+ let fctx = Context::new_future();
+
+ let mut bindings = GcHashMap::new();
+
+ for param in params.iter() {
+ if let Some(v) = ¶m.1 {
+ bindings.insert(
+ param.0.clone(),
+ LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {
+ ctx: fctx.clone(),
+ name: param.0.clone(),
+ value: v.clone(),
+ }))),
+ );
+ } else {
+ bindings.insert(
+ param.0.clone(),
+ LazyVal::new(TraceBox(Box::new(DependsOnUnbound(param.0.clone())))),
+ );
+ }
+ }
+
+ body_ctx
+ .extend(bindings, None, None, None)
+ .into_future(fctx)
+}
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -22,7 +22,6 @@
// For jrsonnet-macros
extern crate self as jrsonnet_evaluator;
-mod builtin;
mod ctx;
mod dynamic;
pub mod error;
@@ -32,8 +31,8 @@
mod import;
mod integrations;
mod map;
-pub mod native;
mod obj;
+mod stdlib;
pub mod trace;
pub mod typed;
pub mod val;
@@ -41,7 +40,7 @@
use std::{
cell::{Ref, RefCell, RefMut},
collections::HashMap,
- fmt::Debug,
+ fmt::{self, Debug},
path::{Path, PathBuf},
rc::Rc,
};
@@ -50,7 +49,7 @@
pub use dynamic::*;
use error::{Error::*, LocError, Result, StackTraceElement};
pub use evaluate::*;
-use function::{Builtin, CallLocation, TlaArg};
+use function::{builtin::Builtin, CallLocation, TlaArg};
use gc::{GcHashMap, TraceBox};
use gcmodule::{Cc, Trace, Weak};
pub use import::*;
@@ -77,7 +76,7 @@
}
impl Debug for LazyBinding {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "LazyBinding")
}
}
@@ -329,7 +328,7 @@
self.add_parsed_file(
std_path.clone(),
STDLIB_STR.to_owned().into(),
- builtin::get_parsed_stdlib(),
+ stdlib::get_parsed_stdlib(),
)
.expect("stdlib is correct");
let val = self
crates/jrsonnet-evaluator/src/native.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/native.rs
+++ /dev/null
@@ -1,49 +0,0 @@
-#![allow(clippy::type_complexity)]
-
-use std::{path::Path, rc::Rc};
-
-use gcmodule::Trace;
-
-use crate::{
- error::Result,
- function::{parse_builtin_call, ArgsLike, Builtin, BuiltinParam, CallLocation},
- gc::TraceBox,
- Context, State, Val,
-};
-
-#[derive(Trace)]
-pub struct NativeCallback {
- pub(crate) params: Vec<BuiltinParam>,
- handler: TraceBox<dyn NativeCallbackHandler>,
-}
-impl NativeCallback {
- #[deprecated = "prefer using builtins directly, use this interface only for bindings"]
- pub fn new(params: Vec<BuiltinParam>, handler: TraceBox<dyn NativeCallbackHandler>) -> Self {
- Self { params, handler }
- }
-}
-
-impl Builtin for NativeCallback {
- fn name(&self) -> &str {
- // TODO: standard natives gets their names from definition
- // But builitins should already have them
- "<native>"
- }
-
- fn params(&self) -> &[BuiltinParam] {
- &self.params
- }
-
- fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
- let args = parse_builtin_call(s.clone(), ctx, &self.params, args, true)?;
- let mut out_args = Vec::with_capacity(self.params.len());
- for p in &self.params {
- out_args.push(args[&p.name].evaluate(s.clone())?);
- }
- self.handler.call(s, loc.0.map(|l| l.0.clone()), &out_args)
- }
-}
-
-pub trait NativeCallbackHandler: Trace {
- fn call(&self, s: State, from: Option<Rc<Path>>, args: &[Val]) -> Result<Val>;
-}
crates/jrsonnet-evaluator/src/stdlib/expr.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/stdlib/expr.rs
@@ -0,0 +1,28 @@
+use std::path::PathBuf;
+
+use jrsonnet_parser::{LocExpr, ParserSettings};
+
+thread_local! {
+ /// To avoid parsing again when issued from the same thread
+ #[allow(unreachable_code)]
+ static PARSED_STDLIB: LocExpr = {
+ #[cfg(feature = "serialized-stdlib")]
+ {
+ // Should not panic, stdlib.bincode is generated in build.rs
+ return bincode::deserialize(include_bytes!(concat!(env!("OUT_DIR"), "/stdlib.bincode")))
+ .unwrap();
+ }
+
+ jrsonnet_parser::parse(
+ jrsonnet_stdlib::STDLIB_STR,
+ &ParserSettings {
+ file_name: PathBuf::from("std.jsonnet").into(),
+ },
+ )
+ .unwrap()
+ }
+}
+
+pub fn get_parsed_stdlib() -> LocExpr {
+ PARSED_STDLIB.with(Clone::clone)
+}
crates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/stdlib/format.rs
@@ -0,0 +1,796 @@
+//! faster std.format impl
+#![allow(clippy::too_many_arguments)]
+
+use gcmodule::Trace;
+use jrsonnet_interner::IStr;
+use jrsonnet_types::ValType;
+use thiserror::Error;
+
+use crate::{error::Error::*, throw, typed::Typed, LocError, ObjValue, Result, State, Val};
+
+#[derive(Debug, Clone, Error, Trace)]
+pub enum FormatError {
+ #[error("truncated format code")]
+ TruncatedFormatCode,
+ #[error("unrecognized conversion type: {0}")]
+ UnrecognizedConversionType(char),
+
+ #[error("not enough values")]
+ NotEnoughValues,
+
+ #[error("cannot use * width with object")]
+ CannotUseStarWidthWithObject,
+ #[error("mapping keys required")]
+ MappingKeysRequired,
+ #[error("no such format field: {0}")]
+ NoSuchFormatField(IStr),
+}
+
+impl From<FormatError> for LocError {
+ fn from(e: FormatError) -> Self {
+ Self::new(Format(e))
+ }
+}
+
+use FormatError::*;
+
+type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>;
+
+pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> {
+ if str.is_empty() {
+ return Err(TruncatedFormatCode);
+ }
+ let bytes = str.as_bytes();
+ if bytes[0] == b'(' {
+ let mut i = 1;
+ while i < bytes.len() {
+ if bytes[i] == b')' {
+ return Ok((&str[1..i as usize], &str[i as usize + 1..]));
+ }
+ i += 1;
+ }
+ Err(TruncatedFormatCode)
+ } else {
+ Ok(("", str))
+ }
+}
+
+#[cfg(test)]
+pub mod tests_key {
+ use super::*;
+
+ #[test]
+ fn parse_key() {
+ assert_eq!(
+ try_parse_mapping_key("(hello ) world").unwrap(),
+ ("hello ", " world")
+ );
+ assert_eq!(try_parse_mapping_key("() world").unwrap(), ("", " world"));
+ assert_eq!(try_parse_mapping_key(" world").unwrap(), ("", " world"));
+ assert_eq!(
+ try_parse_mapping_key(" () world").unwrap(),
+ ("", " () world")
+ );
+ }
+
+ #[test]
+ #[should_panic]
+ fn parse_key_missing_start() {
+ try_parse_mapping_key("").unwrap();
+ }
+
+ #[test]
+ #[should_panic]
+ fn parse_key_missing_end() {
+ try_parse_mapping_key("( ").unwrap();
+ }
+}
+
+#[allow(clippy::struct_excessive_bools)]
+#[derive(Default, Debug)]
+pub struct CFlags {
+ pub alt: bool,
+ pub zero: bool,
+ pub left: bool,
+ pub blank: bool,
+ pub sign: bool,
+}
+
+pub fn try_parse_cflags(str: &str) -> ParseResult<CFlags> {
+ if str.is_empty() {
+ return Err(TruncatedFormatCode);
+ }
+ let bytes = str.as_bytes();
+ let mut i = 0;
+ let mut out = CFlags::default();
+ loop {
+ if bytes.len() == i {
+ return Err(TruncatedFormatCode);
+ }
+ match bytes[i] {
+ b'#' => out.alt = true,
+ b'0' => out.zero = true,
+ b'-' => out.left = true,
+ b' ' => out.blank = true,
+ b'+' => out.sign = true,
+ _ => break,
+ }
+ i += 1;
+ }
+ Ok((out, &str[i..]))
+}
+
+#[derive(Debug, PartialEq)]
+pub enum Width {
+ Star,
+ Fixed(usize),
+}
+pub fn try_parse_field_width(str: &str) -> ParseResult<Width> {
+ if str.is_empty() {
+ return Err(TruncatedFormatCode);
+ }
+ let bytes = str.as_bytes();
+ if bytes[0] == b'*' {
+ return Ok((Width::Star, &str[1..]));
+ }
+ let mut out: usize = 0;
+ let mut digits = 0;
+ while let Some(digit) = (bytes[digits] as char).to_digit(10) {
+ out *= 10;
+ out += digit as usize;
+ digits += 1;
+ if digits == bytes.len() {
+ return Err(TruncatedFormatCode);
+ }
+ }
+ Ok((Width::Fixed(out), &str[digits..]))
+}
+
+pub fn try_parse_precision(str: &str) -> ParseResult<Option<Width>> {
+ if str.is_empty() {
+ return Err(TruncatedFormatCode);
+ }
+ let bytes = str.as_bytes();
+ if bytes[0] == b'.' {
+ try_parse_field_width(&str[1..]).map(|(r, s)| (Some(r), s))
+ } else {
+ Ok((None, str))
+ }
+}
+
+// Only skips
+pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> {
+ if str.is_empty() {
+ return Err(TruncatedFormatCode);
+ }
+ let bytes = str.as_bytes();
+ let mut idx = 0;
+ while bytes[idx] == b'h' || bytes[idx] == b'l' || bytes[idx] == b'L' {
+ idx += 1;
+ if bytes.len() == idx {
+ return Err(TruncatedFormatCode);
+ }
+ }
+ Ok(((), &str[idx..]))
+}
+
+#[derive(Debug, PartialEq)]
+pub enum ConvTypeV {
+ Decimal,
+ Octal,
+ Hexadecimal,
+ Scientific,
+ Float,
+ Shorter,
+ Char,
+ String,
+ Percent,
+}
+pub struct ConvType {
+ v: ConvTypeV,
+ caps: bool,
+}
+
+pub fn parse_conversion_type(str: &str) -> ParseResult<ConvType> {
+ if str.is_empty() {
+ return Err(TruncatedFormatCode);
+ }
+
+ let code = str.as_bytes()[0];
+ let v: (ConvTypeV, bool) = match code {
+ b'd' | b'i' | b'u' => (ConvTypeV::Decimal, false),
+ b'o' => (ConvTypeV::Octal, false),
+ b'x' => (ConvTypeV::Hexadecimal, false),
+ b'X' => (ConvTypeV::Hexadecimal, true),
+ b'e' => (ConvTypeV::Scientific, false),
+ b'E' => (ConvTypeV::Scientific, true),
+ b'f' => (ConvTypeV::Float, false),
+ b'F' => (ConvTypeV::Float, true),
+ b'g' => (ConvTypeV::Shorter, false),
+ b'G' => (ConvTypeV::Shorter, true),
+ b'c' => (ConvTypeV::Char, false),
+ b's' => (ConvTypeV::String, false),
+ b'%' => (ConvTypeV::Percent, false),
+ c => return Err(UnrecognizedConversionType(c as char)),
+ };
+
+ Ok((ConvType { v: v.0, caps: v.1 }, &str[1..]))
+}
+
+#[derive(Debug)]
+pub struct Code<'s> {
+ mkey: &'s str,
+ cflags: CFlags,
+ width: Width,
+ precision: Option<Width>,
+ convtype: ConvTypeV,
+ caps: bool,
+}
+pub fn parse_code(str: &str) -> ParseResult<Code> {
+ if str.is_empty() {
+ return Err(TruncatedFormatCode);
+ }
+ let (mkey, str) = try_parse_mapping_key(str)?;
+ let (cflags, str) = try_parse_cflags(str)?;
+ let (width, str) = try_parse_field_width(str)?;
+ let (precision, str) = try_parse_precision(str)?;
+ let (_, str) = try_parse_length_modifier(str)?;
+ let (convtype, str) = parse_conversion_type(str)?;
+
+ Ok((
+ Code {
+ mkey,
+ cflags,
+ width,
+ precision,
+ convtype: convtype.v,
+ caps: convtype.caps,
+ },
+ str,
+ ))
+}
+
+#[derive(Debug)]
+pub enum Element<'s> {
+ String(&'s str),
+ Code(Code<'s>),
+}
+pub fn parse_codes(mut str: &str) -> Result<Vec<Element>> {
+ let mut bytes = str.as_bytes();
+ let mut out = vec![];
+ let mut offset = 0;
+
+ loop {
+ while offset != bytes.len() && bytes[offset] != b'%' {
+ offset += 1;
+ }
+ if offset != 0 {
+ out.push(Element::String(&str[0..offset]));
+ }
+ if offset == bytes.len() {
+ return Ok(out);
+ }
+ str = &str[offset + 1..];
+ let code;
+ (code, str) = parse_code(str)?;
+ bytes = str.as_bytes();
+ offset = 0;
+
+ out.push(Element::Code(code));
+ }
+}
+
+const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
+
+#[inline]
+pub fn render_integer(
+ out: &mut String,
+ iv: i64,
+ padding: usize,
+ precision: usize,
+ blank: bool,
+ sign: bool,
+ radix: i64,
+ prefix: &str,
+ caps: bool,
+) {
+ // Digit char indexes in reverse order, i.e
+ // for radix = 16 and n = 12f: [15, 2, 1]
+ let digits = if iv == 0 {
+ vec![0u8]
+ } else {
+ let mut v = iv.abs();
+ let mut nums = Vec::with_capacity(1);
+ while v > 0 {
+ nums.push((v % radix) as u8);
+ v /= radix;
+ }
+ nums
+ };
+ let neg = iv < 0;
+ let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });
+ let zp2 = zp
+ .max(precision)
+ .saturating_sub(prefix.len() + digits.len());
+
+ if neg {
+ out.push('-');
+ } else if sign {
+ out.push('+');
+ } else if blank {
+ out.push(' ');
+ }
+
+ out.reserve(zp2);
+ for _ in 0..zp2 {
+ out.push('0');
+ }
+ out.push_str(prefix);
+
+ for digit in digits.into_iter().rev() {
+ let ch = NUMBERS[digit as usize] as char;
+ out.push(if caps { ch.to_ascii_uppercase() } else { ch });
+ }
+}
+
+pub fn render_decimal(
+ out: &mut String,
+ iv: i64,
+ padding: usize,
+ precision: usize,
+ blank: bool,
+ sign: bool,
+) {
+ render_integer(out, iv, padding, precision, blank, sign, 10, "", false);
+}
+pub fn render_octal(
+ out: &mut String,
+ iv: i64,
+ padding: usize,
+ precision: usize,
+ alt: bool,
+ blank: bool,
+ sign: bool,
+) {
+ render_integer(
+ out,
+ iv,
+ padding,
+ precision,
+ blank,
+ sign,
+ 8,
+ if alt && iv != 0 { "0" } else { "" },
+ false,
+ );
+}
+
+#[allow(clippy::fn_params_excessive_bools)]
+pub fn render_hexadecimal(
+ out: &mut String,
+ iv: i64,
+ padding: usize,
+ precision: usize,
+ alt: bool,
+ blank: bool,
+ sign: bool,
+ caps: bool,
+) {
+ render_integer(
+ out,
+ iv,
+ padding,
+ precision,
+ blank,
+ sign,
+ 16,
+ match (alt, caps) {
+ (true, true) => "0X",
+ (true, false) => "0x",
+ (false, _) => "",
+ },
+ caps,
+ );
+}
+
+#[allow(clippy::fn_params_excessive_bools)]
+pub fn render_float(
+ out: &mut String,
+ n: f64,
+ mut padding: usize,
+ precision: usize,
+ blank: bool,
+ sign: bool,
+ ensure_pt: bool,
+ trailing: bool,
+) {
+ let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };
+ padding = padding.saturating_sub(dot_size + precision);
+ render_decimal(out, n.floor() as i64, padding, 0, blank, sign);
+ if precision == 0 {
+ if ensure_pt {
+ out.push('.');
+ }
+ return;
+ }
+ let frac = n
+ .fract()
+ .mul_add(10.0_f64.powf(precision as f64), 0.5)
+ .floor();
+ if trailing || frac > 0.0 {
+ out.push('.');
+ let mut frac_str = String::new();
+ render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);
+ let mut trim = frac_str.len();
+ if !trailing {
+ for b in frac_str.as_bytes().iter().rev() {
+ if *b == b'0' {
+ trim -= 1;
+ }
+ }
+ }
+ out.push_str(&frac_str[..trim]);
+ } else if ensure_pt {
+ out.push('.');
+ }
+}
+
+#[allow(clippy::fn_params_excessive_bools)]
+pub fn render_float_sci(
+ out: &mut String,
+ n: f64,
+ mut padding: usize,
+ precision: usize,
+ blank: bool,
+ sign: bool,
+ ensure_pt: bool,
+ trailing: bool,
+ caps: bool,
+) {
+ let exponent = n.log10().floor();
+ let mantissa = if exponent as i16 == -324 {
+ n * 10.0 / 10.0_f64.powf(exponent + 1.0)
+ } else {
+ n / 10.0_f64.powf(exponent)
+ };
+ let mut exponent_str = String::new();
+ render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);
+
+ // +1 for e
+ padding = padding.saturating_sub(exponent_str.len() + 1);
+
+ render_float(
+ out, mantissa, padding, precision, blank, sign, ensure_pt, trailing,
+ );
+ out.push(if caps { 'E' } else { 'e' });
+ out.push_str(&exponent_str);
+}
+
+#[allow(clippy::too_many_lines)]
+pub fn format_code(
+ s: State,
+ out: &mut String,
+ value: &Val,
+ code: &Code,
+ width: usize,
+ precision: Option<usize>,
+) -> Result<()> {
+ let clfags = &code.cflags;
+ let (fpprec, iprec) = match precision {
+ Some(v) => (v, v),
+ None => (6, 0),
+ };
+ let padding = if clfags.zero && !clfags.left {
+ width
+ } else {
+ 0
+ };
+
+ // TODO: If left padded, can optimize by writing directly to out
+ let mut tmp_out = String::new();
+
+ match code.convtype {
+ ConvTypeV::String => tmp_out.push_str(&value.clone().to_string(s)?),
+ ConvTypeV::Decimal => {
+ let value = f64::from_untyped(value.clone(), s)?;
+ render_decimal(
+ &mut tmp_out,
+ value as i64,
+ padding,
+ iprec,
+ clfags.blank,
+ clfags.sign,
+ );
+ }
+ ConvTypeV::Octal => {
+ let value = f64::from_untyped(value.clone(), s)?;
+ render_octal(
+ &mut tmp_out,
+ value as i64,
+ padding,
+ iprec,
+ clfags.alt,
+ clfags.blank,
+ clfags.sign,
+ );
+ }
+ ConvTypeV::Hexadecimal => {
+ let value = f64::from_untyped(value.clone(), s)?;
+ render_hexadecimal(
+ &mut tmp_out,
+ value as i64,
+ padding,
+ iprec,
+ clfags.alt,
+ clfags.blank,
+ clfags.sign,
+ code.caps,
+ );
+ }
+ ConvTypeV::Scientific => {
+ let value = f64::from_untyped(value.clone(), s)?;
+ render_float_sci(
+ &mut tmp_out,
+ value,
+ padding,
+ fpprec,
+ clfags.blank,
+ clfags.sign,
+ clfags.alt,
+ true,
+ code.caps,
+ );
+ }
+ ConvTypeV::Float => {
+ let value = f64::from_untyped(value.clone(), s)?;
+ render_float(
+ &mut tmp_out,
+ value,
+ padding,
+ fpprec,
+ clfags.blank,
+ clfags.sign,
+ clfags.alt,
+ true,
+ );
+ }
+ ConvTypeV::Shorter => {
+ let value = f64::from_untyped(value.clone(), s)?;
+ let exponent = value.log10().floor();
+ if exponent < -4.0 || exponent >= fpprec as f64 {
+ render_float_sci(
+ &mut tmp_out,
+ value,
+ padding,
+ fpprec - 1,
+ clfags.blank,
+ clfags.sign,
+ clfags.alt,
+ clfags.alt,
+ code.caps,
+ );
+ } else {
+ let digits_before_pt = 1.max(exponent as usize + 1);
+ render_float(
+ &mut tmp_out,
+ value,
+ padding,
+ fpprec - digits_before_pt,
+ clfags.blank,
+ clfags.sign,
+ clfags.alt,
+ clfags.alt,
+ );
+ }
+ }
+ ConvTypeV::Char => match value.clone() {
+ Val::Num(n) => tmp_out
+ .push(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?),
+ Val::Str(s) => {
+ if s.chars().count() != 1 {
+ throw!(RuntimeError(
+ format!("%c expected 1 char string, got {}", s.chars().count()).into(),
+ ));
+ }
+ tmp_out.push_str(&s);
+ }
+ _ => {
+ throw!(TypeMismatch(
+ "%c requires number/string",
+ vec![ValType::Num, ValType::Str],
+ value.value_type(),
+ ));
+ }
+ },
+ ConvTypeV::Percent => tmp_out.push('%'),
+ };
+
+ let padding = width.saturating_sub(tmp_out.len());
+
+ if !clfags.left {
+ for _ in 0..padding {
+ out.push(' ');
+ }
+ }
+ out.push_str(&tmp_out);
+ if clfags.left {
+ for _ in 0..padding {
+ out.push(' ');
+ }
+ }
+
+ Ok(())
+}
+
+pub fn format_arr(s: State, str: &str, mut values: &[Val]) -> Result<String> {
+ let codes = parse_codes(str)?;
+ let mut out = String::new();
+
+ for code in codes {
+ match code {
+ Element::String(s) => {
+ out.push_str(s);
+ }
+ Element::Code(c) => {
+ let width = match c.width {
+ Width::Star => {
+ if values.is_empty() {
+ throw!(NotEnoughValues);
+ }
+ let value = &values[0];
+ values = &values[1..];
+ usize::from_untyped(value.clone(), s.clone())?
+ }
+ Width::Fixed(n) => n,
+ };
+ let precision = match c.precision {
+ Some(Width::Star) => {
+ if values.is_empty() {
+ throw!(NotEnoughValues);
+ }
+ let value = &values[0];
+ values = &values[1..];
+ Some(usize::from_untyped(value.clone(), s.clone())?)
+ }
+ Some(Width::Fixed(n)) => Some(n),
+ None => None,
+ };
+
+ // %% should not consume a value
+ let value = if c.convtype == ConvTypeV::Percent {
+ &Val::Null
+ } else {
+ if values.is_empty() {
+ throw!(NotEnoughValues);
+ }
+ let value = &values[0];
+ values = &values[1..];
+ value
+ };
+
+ format_code(s.clone(), &mut out, value, &c, width, precision)?;
+ }
+ }
+ }
+
+ Ok(out)
+}
+
+pub fn format_obj(s: State, str: &str, values: &ObjValue) -> Result<String> {
+ let codes = parse_codes(str)?;
+ let mut out = String::new();
+
+ for code in codes {
+ match code {
+ Element::String(s) => {
+ out.push_str(s);
+ }
+ Element::Code(c) => {
+ // TODO: Operate on ref
+ let f: IStr = c.mkey.into();
+ let width = match c.width {
+ Width::Star => {
+ throw!(CannotUseStarWidthWithObject);
+ }
+ Width::Fixed(n) => n,
+ };
+ let precision = match c.precision {
+ Some(Width::Star) => {
+ throw!(CannotUseStarWidthWithObject);
+ }
+ Some(Width::Fixed(n)) => Some(n),
+ None => None,
+ };
+
+ let value = if c.convtype == ConvTypeV::Percent {
+ Val::Null
+ } else {
+ if f.is_empty() {
+ throw!(MappingKeysRequired);
+ }
+ if let Some(v) = values.get(s.clone(), f.clone())? {
+ v
+ } else {
+ throw!(NoSuchFormatField(f));
+ }
+ };
+
+ format_code(s.clone(), &mut out, &value, &c, width, precision)?;
+ }
+ }
+ }
+
+ Ok(out)
+}
+
+#[cfg(test)]
+pub mod test_format {
+ use super::*;
+
+ #[test]
+ fn parse() {
+ assert_eq!(
+ parse_codes(
+ "How much error budget is left looking at our %.3f%% availability gurantees?"
+ )
+ .unwrap()
+ .len(),
+ 4
+ );
+ }
+
+ #[test]
+ fn octals() {
+ let s = State::default();
+ assert_eq!(
+ format_arr(s.clone(), "%#o", &[Val::Num(8.0)]).unwrap(),
+ "010"
+ );
+ assert_eq!(
+ format_arr(s.clone(), "%#4o", &[Val::Num(8.0)]).unwrap(),
+ " 010"
+ );
+ assert_eq!(
+ format_arr(s.clone(), "%4o", &[Val::Num(8.0)]).unwrap(),
+ " 10"
+ );
+ assert_eq!(
+ format_arr(s.clone(), "%04o", &[Val::Num(8.0)]).unwrap(),
+ "0010"
+ );
+ assert_eq!(
+ format_arr(s.clone(), "%+4o", &[Val::Num(8.0)]).unwrap(),
+ " +10"
+ );
+ assert_eq!(
+ format_arr(s.clone(), "%+04o", &[Val::Num(8.0)]).unwrap(),
+ "+010"
+ );
+ assert_eq!(
+ format_arr(s.clone(), "%-4o", &[Val::Num(8.0)]).unwrap(),
+ "10 "
+ );
+ assert_eq!(
+ format_arr(s.clone(), "%+-4o", &[Val::Num(8.0)]).unwrap(),
+ "+10 "
+ );
+ assert_eq!(
+ format_arr(s.clone(), "%+-04o", &[Val::Num(8.0)]).unwrap(),
+ "+10 "
+ );
+ }
+
+ #[test]
+ fn percent_doesnt_consumes_values() {
+ let s = State::default();
+ assert_eq!(
+ format_arr(
+ s,
+ "How much error budget is left looking at our %.3f%% availability gurantees?",
+ &[Val::Num(4.0)]
+ )
+ .unwrap(),
+ "How much error budget is left looking at our 4.000% availability gurantees?"
+ );
+ }
+}
crates/jrsonnet-evaluator/src/stdlib/manifest.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/stdlib/manifest.rs
@@ -0,0 +1,347 @@
+use crate::{
+ error::{Error::*, Result},
+ throw, State, Val,
+};
+
+#[derive(PartialEq, Clone, Copy)]
+pub enum ManifestType {
+ // Applied in manifestification
+ Manifest,
+ /// Used for std.manifestJson
+ /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest
+ Std,
+ /// No line breaks, used in `obj+''`
+ ToString,
+ /// Minified json
+ Minify,
+}
+
+pub struct ManifestJsonOptions<'s> {
+ pub padding: &'s str,
+ pub mtype: ManifestType,
+ pub newline: &'s str,
+ pub key_val_sep: &'s str,
+ #[cfg(feature = "exp-preserve-order")]
+ pub preserve_order: bool,
+}
+
+pub fn manifest_json_ex(s: State, val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {
+ let mut out = String::new();
+ manifest_json_ex_buf(s, val, &mut out, &mut String::new(), options)?;
+ Ok(out)
+}
+fn manifest_json_ex_buf(
+ s: State,
+ val: &Val,
+ buf: &mut String,
+ cur_padding: &mut String,
+ options: &ManifestJsonOptions<'_>,
+) -> Result<()> {
+ use std::fmt::Write;
+ let mtype = options.mtype;
+ match val {
+ Val::Bool(v) => {
+ if *v {
+ buf.push_str("true");
+ } else {
+ buf.push_str("false");
+ }
+ }
+ Val::Null => buf.push_str("null"),
+ Val::Str(s) => escape_string_json_buf(s, buf),
+ Val::Num(n) => write!(buf, "{}", n).unwrap(),
+ Val::Arr(items) => {
+ buf.push('[');
+ if !items.is_empty() {
+ if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
+ buf.push_str(options.newline);
+ }
+
+ let old_len = cur_padding.len();
+ cur_padding.push_str(options.padding);
+ for (i, item) in items.iter(s.clone()).enumerate() {
+ if i != 0 {
+ buf.push(',');
+ if mtype == ManifestType::ToString {
+ buf.push(' ');
+ } else if mtype != ManifestType::Minify {
+ buf.push_str(options.newline);
+ }
+ }
+ buf.push_str(cur_padding);
+ manifest_json_ex_buf(s.clone(), &item?, buf, cur_padding, options)?;
+ }
+ cur_padding.truncate(old_len);
+
+ if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
+ buf.push_str(options.newline);
+ buf.push_str(cur_padding);
+ }
+ } else if mtype == ManifestType::Std {
+ buf.push_str("\n\n");
+ buf.push_str(cur_padding);
+ } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {
+ buf.push(' ');
+ }
+ buf.push(']');
+ }
+ Val::Obj(obj) => {
+ obj.run_assertions(s.clone())?;
+ buf.push('{');
+ let fields = obj.fields(
+ #[cfg(feature = "exp-preserve-order")]
+ options.preserve_order,
+ );
+ if !fields.is_empty() {
+ if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
+ buf.push_str(options.newline);
+ }
+
+ let old_len = cur_padding.len();
+ cur_padding.push_str(options.padding);
+ for (i, field) in fields.into_iter().enumerate() {
+ if i != 0 {
+ buf.push(',');
+ if mtype == ManifestType::ToString {
+ buf.push(' ');
+ } else if mtype != ManifestType::Minify {
+ buf.push_str(options.newline);
+ }
+ }
+ buf.push_str(cur_padding);
+ escape_string_json_buf(&field, buf);
+ buf.push_str(options.key_val_sep);
+ s.push_description(
+ || format!("field <{}> manifestification", field.clone()),
+ || {
+ let value = obj.get(s.clone(), field.clone())?.unwrap();
+ manifest_json_ex_buf(s.clone(), &value, buf, cur_padding, options)?;
+ Ok(Val::Null)
+ },
+ )?;
+ }
+ cur_padding.truncate(old_len);
+
+ if mtype != ManifestType::ToString && mtype != ManifestType::Minify {
+ buf.push_str(options.newline);
+ buf.push_str(cur_padding);
+ }
+ } else if mtype == ManifestType::Std {
+ buf.push_str("\n\n");
+ buf.push_str(cur_padding);
+ } else if mtype == ManifestType::ToString || mtype == ManifestType::Manifest {
+ buf.push(' ');
+ }
+ buf.push('}');
+ }
+ Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),
+ };
+ Ok(())
+}
+
+pub fn escape_string_json(s: &str) -> String {
+ let mut buf = String::new();
+ escape_string_json_buf(s, &mut buf);
+ buf
+}
+
+fn escape_string_json_buf(s: &str, buf: &mut String) {
+ use std::fmt::Write;
+ buf.push('"');
+ for c in s.chars() {
+ match c {
+ '"' => buf.push_str("\\\""),
+ '\\' => buf.push_str("\\\\"),
+ '\u{0008}' => buf.push_str("\\b"),
+ '\u{000c}' => buf.push_str("\\f"),
+ '\n' => buf.push_str("\\n"),
+ '\r' => buf.push_str("\\r"),
+ '\t' => buf.push_str("\\t"),
+ c if c < 32 as char || (c >= 127 as char && c <= 159 as char) => {
+ write!(buf, "\\u{:04x}", c as u32).unwrap();
+ }
+ c => buf.push(c),
+ }
+ }
+ buf.push('"');
+}
+
+pub struct ManifestYamlOptions<'s> {
+ /// Padding before fields, i.e
+ /// ```yaml
+ /// a:
+ /// b:
+ /// ## <- this
+ /// ```
+ pub padding: &'s str,
+ /// Padding before array elements in objects
+ /// ```yaml
+ /// a:
+ /// - 1
+ /// ## <- this
+ /// ```
+ pub arr_element_padding: &'s str,
+ /// Should yaml keys appear unescaped, when possible
+ /// ```yaml
+ /// "safe_key": 1
+ /// # vs
+ /// safe_key: 1
+ /// ```
+ pub quote_keys: bool,
+ /// If true - then order of fields is preserved as written,
+ /// instead of sorting alphabetically
+ #[cfg(feature = "exp-preserve-order")]
+ pub preserve_order: bool,
+}
+
+/// From <https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289>
+/// With added date check
+fn yaml_needs_quotes(string: &str) -> bool {
+ fn need_quotes_spaces(string: &str) -> bool {
+ string.starts_with(' ') || string.ends_with(' ')
+ }
+
+ string.is_empty()
+ || need_quotes_spaces(string)
+ || string.starts_with(|c| matches!(c, '&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@'))
+ || string.contains(|c| matches!(c, ':' | '{' | '}' | '[' | ']' | ',' | '#' | '`' | '\"' | '\'' | '\\' | '\0'..='\x06' | '\t' | '\n' | '\r' | '\x0e'..='\x1a' | '\x1c'..='\x1f'))
+ || [
+ // http://yaml.org/type/bool.html
+ // Note: 'y', 'Y', 'n', 'N', is not quoted deliberately, as in libyaml. PyYAML also parse
+ // them as string, not booleans, although it is violating the YAML 1.1 specification.
+ // See https://github.com/dtolnay/serde-yaml/pull/83#discussion_r152628088.
+ "yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE", "false",
+ "on", "On", "ON", "off", "Off", "OFF", // http://yaml.org/type/null.html
+ "null", "Null", "NULL", "~",
+ ].contains(&string)
+ || (string.chars().all(|c| matches!(c, '0'..='9' | '-'))
+ && string.chars().filter(|c| *c == '-').count() == 2)
+ || string.starts_with('.')
+ || string.starts_with("0x")
+ || string.parse::<i64>().is_ok()
+ || string.parse::<f64>().is_ok()
+}
+
+pub fn manifest_yaml_ex(s: State, val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {
+ let mut out = String::new();
+ manifest_yaml_ex_buf(s, val, &mut out, &mut String::new(), options)?;
+ Ok(out)
+}
+
+#[allow(clippy::too_many_lines)]
+fn manifest_yaml_ex_buf(
+ s: State,
+ val: &Val,
+ buf: &mut String,
+ cur_padding: &mut String,
+ options: &ManifestYamlOptions<'_>,
+) -> Result<()> {
+ use std::fmt::Write;
+ match val {
+ Val::Bool(v) => {
+ if *v {
+ buf.push_str("true");
+ } else {
+ buf.push_str("false");
+ }
+ }
+ Val::Null => buf.push_str("null"),
+ Val::Str(s) => {
+ if s.is_empty() {
+ buf.push_str("\"\"");
+ } else if let Some(s) = s.strip_suffix('\n') {
+ buf.push('|');
+ for line in s.split('\n') {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ buf.push_str(options.padding);
+ buf.push_str(line);
+ }
+ } else if !options.quote_keys && !yaml_needs_quotes(s) {
+ buf.push_str(s);
+ } else {
+ escape_string_json_buf(s, buf);
+ }
+ }
+ Val::Num(n) => write!(buf, "{}", *n).unwrap(),
+ Val::Arr(a) => {
+ if a.is_empty() {
+ buf.push_str("[]");
+ } else {
+ for (i, item) in a.iter(s.clone()).enumerate() {
+ if i != 0 {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ }
+ let item = item?;
+ buf.push('-');
+ match &item {
+ Val::Arr(a) if !a.is_empty() => {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ buf.push_str(options.padding);
+ }
+ _ => buf.push(' '),
+ }
+ let extra_padding = match &item {
+ Val::Arr(a) => !a.is_empty(),
+ Val::Obj(o) => !o.is_empty(),
+ _ => false,
+ };
+ let prev_len = cur_padding.len();
+ if extra_padding {
+ cur_padding.push_str(options.padding);
+ }
+ manifest_yaml_ex_buf(s.clone(), &item, buf, cur_padding, options)?;
+ cur_padding.truncate(prev_len);
+ }
+ }
+ }
+ Val::Obj(o) => {
+ if o.is_empty() {
+ buf.push_str("{}");
+ } else {
+ for (i, key) in o
+ .fields(
+ #[cfg(feature = "exp-preserve-order")]
+ options.preserve_order,
+ )
+ .iter()
+ .enumerate()
+ {
+ if i != 0 {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ }
+ if !options.quote_keys && !yaml_needs_quotes(key) {
+ buf.push_str(key);
+ } else {
+ escape_string_json_buf(key, buf);
+ }
+ buf.push(':');
+ let prev_len = cur_padding.len();
+ let item = o.get(s.clone(), key.clone())?.expect("field exists");
+ match &item {
+ Val::Arr(a) if !a.is_empty() => {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ buf.push_str(options.arr_element_padding);
+ cur_padding.push_str(options.arr_element_padding);
+ }
+ Val::Obj(o) if !o.is_empty() => {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ buf.push_str(options.padding);
+ cur_padding.push_str(options.padding);
+ }
+ _ => buf.push(' '),
+ }
+ manifest_yaml_ex_buf(s.clone(), &item, buf, cur_padding, options)?;
+ cur_padding.truncate(prev_len);
+ }
+ }
+ }
+ Val::Func(_) => throw!(RuntimeError("tried to manifest function".into())),
+ }
+ Ok(())
+}
crates/jrsonnet-evaluator/src/stdlib/mod.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/stdlib/mod.rs
@@ -0,0 +1,743 @@
+// All builtins should return results
+#![allow(clippy::unnecessary_wraps)]
+
+use std::collections::HashMap;
+
+use format::{format_arr, format_obj};
+use gcmodule::Cc;
+use jrsonnet_interner::IStr;
+use serde::Deserialize;
+use serde_yaml::DeserializingQuirks;
+
+use crate::{
+ error::{Error::*, Result},
+ function::{builtin::StaticBuiltin, CallLocation, FuncVal},
+ operator::evaluate_mod_op,
+ stdlib::manifest::{manifest_yaml_ex, ManifestYamlOptions},
+ throw,
+ typed::{Any, BoundedUsize, Bytes, Either2, Either4, PositiveF64, Typed, VecVal, M1},
+ val::{equals, primitive_equals, ArrValue, IndexableVal, Slice},
+ Either, ObjValue, State, Val,
+};
+
+pub mod expr;
+pub use expr::*;
+
+use self::manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType};
+
+pub mod format;
+pub mod manifest;
+pub mod sort;
+
+pub fn std_format(s: State, str: IStr, vals: Val) -> Result<String> {
+ s.push(
+ CallLocation::native(),
+ || format!("std.format of {}", str),
+ || {
+ Ok(match vals {
+ Val::Arr(vals) => format_arr(s.clone(), &str, &vals.evaluated(s.clone())?)?,
+ Val::Obj(obj) => format_obj(s.clone(), &str, &obj)?,
+ o => format_arr(s.clone(), &str, &[o])?,
+ })
+ },
+ )
+}
+
+pub fn std_slice(
+ indexable: IndexableVal,
+ index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+ end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+ step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
+) -> Result<Val> {
+ match &indexable {
+ IndexableVal::Str(s) => {
+ let index = index.as_deref().copied().unwrap_or(0);
+ let end = end.as_deref().copied().unwrap_or(usize::MAX);
+ let step = step.as_deref().copied().unwrap_or(1);
+
+ if index >= end {
+ return Ok(Val::Str("".into()));
+ }
+
+ Ok(Val::Str(
+ (s.chars()
+ .skip(index)
+ .take(end - index)
+ .step_by(step)
+ .collect::<String>())
+ .into(),
+ ))
+ }
+ IndexableVal::Arr(arr) => {
+ let index = index.as_deref().copied().unwrap_or(0);
+ let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());
+ let step = step.as_deref().copied().unwrap_or(1);
+
+ if index >= end {
+ return Ok(Val::Arr(ArrValue::new_eager()));
+ }
+
+ Ok(Val::Arr(ArrValue::Slice(Box::new(Slice {
+ inner: arr.clone(),
+ from: index as u32,
+ to: end as u32,
+ step: step as u32,
+ }))))
+ }
+ }
+}
+
+type BuiltinsType = HashMap<IStr, &'static dyn StaticBuiltin>;
+
+thread_local! {
+ pub static BUILTINS: BuiltinsType = {
+ [
+ ("length".into(), builtin_length::INST),
+ ("type".into(), builtin_type::INST),
+ ("makeArray".into(), builtin_make_array::INST),
+ ("codepoint".into(), builtin_codepoint::INST),
+ ("objectFieldsEx".into(), builtin_object_fields_ex::INST),
+ ("objectHasEx".into(), builtin_object_has_ex::INST),
+ ("slice".into(), builtin_slice::INST),
+ ("substr".into(), builtin_substr::INST),
+ ("primitiveEquals".into(), builtin_primitive_equals::INST),
+ ("equals".into(), builtin_equals::INST),
+ ("modulo".into(), builtin_modulo::INST),
+ ("mod".into(), builtin_mod::INST),
+ ("floor".into(), builtin_floor::INST),
+ ("ceil".into(), builtin_ceil::INST),
+ ("log".into(), builtin_log::INST),
+ ("pow".into(), builtin_pow::INST),
+ ("sqrt".into(), builtin_sqrt::INST),
+ ("sin".into(), builtin_sin::INST),
+ ("cos".into(), builtin_cos::INST),
+ ("tan".into(), builtin_tan::INST),
+ ("asin".into(), builtin_asin::INST),
+ ("acos".into(), builtin_acos::INST),
+ ("atan".into(), builtin_atan::INST),
+ ("exp".into(), builtin_exp::INST),
+ ("mantissa".into(), builtin_mantissa::INST),
+ ("exponent".into(), builtin_exponent::INST),
+ ("extVar".into(), builtin_ext_var::INST),
+ ("native".into(), builtin_native::INST),
+ ("filter".into(), builtin_filter::INST),
+ ("map".into(), builtin_map::INST),
+ ("flatMap".into(), builtin_flatmap::INST),
+ ("foldl".into(), builtin_foldl::INST),
+ ("foldr".into(), builtin_foldr::INST),
+ ("sort".into(), builtin_sort::INST),
+ ("format".into(), builtin_format::INST),
+ ("range".into(), builtin_range::INST),
+ ("char".into(), builtin_char::INST),
+ ("encodeUTF8".into(), builtin_encode_utf8::INST),
+ ("decodeUTF8".into(), builtin_decode_utf8::INST),
+ ("md5".into(), builtin_md5::INST),
+ ("base64".into(), builtin_base64::INST),
+ ("base64DecodeBytes".into(), builtin_base64_decode_bytes::INST),
+ ("base64Decode".into(), builtin_base64_decode::INST),
+ ("trace".into(), builtin_trace::INST),
+ ("join".into(), builtin_join::INST),
+ ("escapeStringJson".into(), builtin_escape_string_json::INST),
+ ("manifestJsonEx".into(), builtin_manifest_json_ex::INST),
+ ("manifestYamlDoc".into(), builtin_manifest_yaml_doc::INST),
+ ("reverse".into(), builtin_reverse::INST),
+ ("strReplace".into(), builtin_str_replace::INST),
+ ("splitLimit".into(), builtin_splitlimit::INST),
+ ("parseJson".into(), builtin_parse_json::INST),
+ ("parseYaml".into(), builtin_parse_yaml::INST),
+ ("asciiUpper".into(), builtin_ascii_upper::INST),
+ ("asciiLower".into(), builtin_ascii_lower::INST),
+ ("member".into(), builtin_member::INST),
+ ("count".into(), builtin_count::INST),
+ ("any".into(), builtin_any::INST),
+ ("all".into(), builtin_all::INST),
+ ].iter().cloned().collect()
+ };
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> Result<usize> {
+ use Either4::*;
+ Ok(match x {
+ A(x) => x.chars().count(),
+ B(x) => x.len(),
+ C(x) => x.len(),
+ D(f) => f.params_len(),
+ })
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_type(x: Any) -> Result<IStr> {
+ Ok(x.0.value_type().name().into())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_make_array(s: State, sz: usize, func: FuncVal) -> Result<VecVal> {
+ let mut out = Vec::with_capacity(sz);
+ for i in 0..sz {
+ out.push(func.evaluate_simple(s.clone(), &(i as f64,))?);
+ }
+ Ok(VecVal(Cc::new(out)))
+}
+
+#[jrsonnet_macros::builtin]
+const fn builtin_codepoint(str: char) -> Result<u32> {
+ Ok(str as u32)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_object_fields_ex(
+ obj: ObjValue,
+ inc_hidden: bool,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<VecVal> {
+ #[cfg(feature = "exp-preserve-order")]
+ let preserve_order = preserve_order.unwrap_or(false);
+ let out = obj.fields_ex(
+ inc_hidden,
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ );
+ Ok(VecVal(Cc::new(
+ out.into_iter().map(Val::Str).collect::<Vec<_>>(),
+ )))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_object_has_ex(obj: ObjValue, f: IStr, inc_hidden: bool) -> Result<bool> {
+ Ok(obj.has_field_ex(f, inc_hidden))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_parse_json(st: State, s: IStr) -> Result<Any> {
+ use serde_json::Value;
+ let value: Value = serde_json::from_str(&s)
+ .map_err(|e| RuntimeError(format!("failed to parse json: {}", e).into()))?;
+ Ok(Any(Value::into_untyped(value, st)?))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_parse_yaml(st: State, s: IStr) -> Result<Any> {
+ use serde_json::Value;
+ let value = serde_yaml::Deserializer::from_str_with_quirks(
+ &s,
+ DeserializingQuirks { old_octals: true },
+ );
+ let mut out = vec![];
+ for item in value {
+ let value = Value::deserialize(item)
+ .map_err(|e| RuntimeError(format!("failed to parse yaml: {}", e).into()))?;
+ let val = Value::into_untyped(value, st.clone())?;
+ out.push(val);
+ }
+ Ok(Any(if out.is_empty() {
+ Val::Null
+ } else if out.len() == 1 {
+ out.into_iter().next().unwrap()
+ } else {
+ Val::Arr(out.into())
+ }))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_slice(
+ indexable: IndexableVal,
+ index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+ end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+ step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
+) -> Result<Any> {
+ std_slice(indexable, index, end, step).map(Any)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_substr(str: IStr, from: usize, len: usize) -> Result<String> {
+ Ok(str.chars().skip(from as usize).take(len as usize).collect())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_primitive_equals(a: Any, b: Any) -> Result<bool> {
+ primitive_equals(&a.0, &b.0)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_equals(s: State, a: Any, b: Any) -> Result<bool> {
+ equals(s, &a.0, &b.0)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_modulo(a: f64, b: f64) -> Result<f64> {
+ Ok(a % b)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_mod(s: State, a: Either![f64, IStr], b: Any) -> Result<Any> {
+ use Either2::*;
+ Ok(Any(evaluate_mod_op(
+ s,
+ &match a {
+ A(v) => Val::Num(v),
+ B(s) => Val::Str(s),
+ },
+ &b.0,
+ )?))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_floor(x: f64) -> Result<f64> {
+ Ok(x.floor())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_ceil(x: f64) -> Result<f64> {
+ Ok(x.ceil())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_log(n: f64) -> Result<f64> {
+ Ok(n.ln())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_pow(x: f64, n: f64) -> Result<f64> {
+ Ok(x.powf(n))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_sqrt(x: PositiveF64) -> Result<f64> {
+ Ok(x.0.sqrt())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_sin(x: f64) -> Result<f64> {
+ Ok(x.sin())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_cos(x: f64) -> Result<f64> {
+ Ok(x.cos())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_tan(x: f64) -> Result<f64> {
+ Ok(x.tan())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_asin(x: f64) -> Result<f64> {
+ Ok(x.asin())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_acos(x: f64) -> Result<f64> {
+ Ok(x.acos())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_atan(x: f64) -> Result<f64> {
+ Ok(x.atan())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_exp(x: f64) -> Result<f64> {
+ Ok(x.exp())
+}
+
+fn frexp(s: f64) -> (f64, i16) {
+ if 0.0 == s {
+ (s, 0)
+ } else {
+ let lg = s.abs().log2();
+ let x = (lg - lg.floor() - 1.0).exp2();
+ let exp = lg.floor() + 1.0;
+ (s.signum() * x, exp as i16)
+ }
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_mantissa(x: f64) -> Result<f64> {
+ Ok(frexp(x).0)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_exponent(x: f64) -> Result<i16> {
+ Ok(frexp(x).1)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_ext_var(s: State, x: IStr) -> Result<Any> {
+ Ok(Any(s
+ .settings()
+ .ext_vars
+ .get(&x)
+ .cloned()
+ .ok_or(UndefinedExternalVariable(x))?))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_native(s: State, name: IStr) -> Result<Any> {
+ Ok(Any(s
+ .settings()
+ .ext_natives
+ .get(&name)
+ .cloned()
+ .map_or(Val::Null, |v| {
+ Val::Func(FuncVal::Builtin(v.clone()))
+ })))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_filter(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
+ arr.filter(s.clone(), |val| {
+ bool::from_untyped(
+ func.evaluate_simple(s.clone(), &(Any(val.clone()),))?,
+ s.clone(),
+ )
+ })
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_map(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
+ arr.map(s.clone(), |val| {
+ func.evaluate_simple(s.clone(), &(Any(val),))
+ })
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_flatmap(s: State, func: FuncVal, arr: IndexableVal) -> Result<IndexableVal> {
+ match arr {
+ IndexableVal::Str(str) => {
+ let mut out = String::new();
+ for c in str.chars() {
+ match func.evaluate_simple(s.clone(), &(c.to_string(),))? {
+ Val::Str(o) => out.push_str(&o),
+ Val::Null => continue,
+ _ => throw!(RuntimeError(
+ "in std.join all items should be strings".into()
+ )),
+ };
+ }
+ Ok(IndexableVal::Str(out.into()))
+ }
+ IndexableVal::Arr(a) => {
+ let mut out = Vec::new();
+ for el in a.iter(s.clone()) {
+ let el = el?;
+ match func.evaluate_simple(s.clone(), &(Any(el),))? {
+ Val::Arr(o) => {
+ for oe in o.iter(s.clone()) {
+ out.push(oe?);
+ }
+ }
+ Val::Null => continue,
+ _ => throw!(RuntimeError(
+ "in std.join all items should be arrays".into()
+ )),
+ };
+ }
+ Ok(IndexableVal::Arr(out.into()))
+ }
+ }
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_foldl(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
+ let mut acc = init.0;
+ for i in arr.iter(s.clone()) {
+ acc = func.evaluate_simple(s.clone(), &(Any(acc), Any(i?)))?;
+ }
+ Ok(Any(acc))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_foldr(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
+ let mut acc = init.0;
+ for i in arr.iter(s.clone()).rev() {
+ acc = func.evaluate_simple(s.clone(), &(Any(i?), Any(acc)))?;
+ }
+ Ok(Any(acc))
+}
+
+#[jrsonnet_macros::builtin]
+#[allow(non_snake_case)]
+fn builtin_sort(s: State, arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
+ if arr.len() <= 1 {
+ return Ok(arr);
+ }
+ Ok(ArrValue::Eager(sort::sort(
+ s.clone(),
+ arr.evaluated(s)?,
+ keyF.unwrap_or(FuncVal::identity()),
+ )?))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_format(s: State, str: IStr, vals: Any) -> Result<String> {
+ std_format(s, str, vals.0)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_range(from: i32, to: i32) -> Result<ArrValue> {
+ if to < from {
+ return Ok(ArrValue::new_eager());
+ }
+ Ok(ArrValue::new_range(from, to))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_char(n: u32) -> Result<char> {
+ Ok(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_encode_utf8(str: IStr) -> Result<Bytes> {
+ Ok(Bytes(str.bytes().collect::<Vec<u8>>().into()))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_decode_utf8(arr: Bytes) -> Result<IStr> {
+ Ok(std::str::from_utf8(&arr.0)
+ .map_err(|_| RuntimeError("bad utf8".into()))?
+ .into())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_md5(str: IStr) -> Result<String> {
+ Ok(format!("{:x}", md5::compute(&str.as_bytes())))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_trace(s: State, loc: CallLocation, str: IStr, rest: Any) -> Result<Any> {
+ eprint!("TRACE:");
+ if let Some(loc) = loc.0 {
+ 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) as Result<Any>
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_base64(input: Either![Bytes, IStr]) -> Result<String> {
+ use Either2::*;
+ Ok(match input {
+ A(a) => base64::encode(a.0),
+ B(l) => base64::encode(l.bytes().collect::<Vec<_>>()),
+ })
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_base64_decode_bytes(input: IStr) -> Result<Bytes> {
+ Ok(Bytes(
+ base64::decode(&input.as_bytes())
+ .map_err(|_| RuntimeError("bad base64".into()))?
+ .into(),
+ ))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_base64_decode(input: IStr) -> Result<String> {
+ let bytes = base64::decode(&input.as_bytes()).map_err(|_| RuntimeError("bad base64".into()))?;
+ Ok(String::from_utf8(bytes).map_err(|_| RuntimeError("bad utf8".into()))?)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_join(s: State, sep: IndexableVal, arr: ArrValue) -> Result<IndexableVal> {
+ Ok(match sep {
+ IndexableVal::Arr(joiner_items) => {
+ let mut out = Vec::new();
+
+ let mut first = true;
+ for item in arr.iter(s.clone()) {
+ let item = item?.clone();
+ if let Val::Arr(items) = item {
+ if !first {
+ out.reserve(joiner_items.len());
+ // TODO: extend
+ for item in joiner_items.iter(s.clone()) {
+ out.push(item?);
+ }
+ }
+ first = false;
+ out.reserve(items.len());
+ for item in items.iter(s.clone()) {
+ out.push(item?);
+ }
+ } else if matches!(item, Val::Null) {
+ continue;
+ } else {
+ throw!(RuntimeError(
+ "in std.join all items should be arrays".into()
+ ));
+ }
+ }
+
+ IndexableVal::Arr(out.into())
+ }
+ IndexableVal::Str(sep) => {
+ let mut out = String::new();
+
+ let mut first = true;
+ for item in arr.iter(s) {
+ let item = item?.clone();
+ if let Val::Str(item) = item {
+ if !first {
+ out += &sep;
+ }
+ first = false;
+ out += &item;
+ } else if matches!(item, Val::Null) {
+ continue;
+ } else {
+ throw!(RuntimeError(
+ "in std.join all items should be strings".into()
+ ));
+ }
+ }
+
+ IndexableVal::Str(out.into())
+ }
+ })
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_escape_string_json(str_: IStr) -> Result<String> {
+ Ok(escape_string_json(&str_))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_manifest_json_ex(
+ s: State,
+ value: Any,
+ indent: IStr,
+ newline: Option<IStr>,
+ key_val_sep: Option<IStr>,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<String> {
+ let newline = newline.as_deref().unwrap_or("\n");
+ let key_val_sep = key_val_sep.as_deref().unwrap_or(": ");
+ manifest_json_ex(
+ s,
+ &value.0,
+ &ManifestJsonOptions {
+ padding: &indent,
+ mtype: ManifestType::Std,
+ newline,
+ key_val_sep,
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: preserve_order.unwrap_or(false),
+ },
+ )
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_manifest_yaml_doc(
+ s: State,
+ value: Any,
+ indent_array_in_object: Option<bool>,
+ quote_keys: Option<bool>,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<String> {
+ manifest_yaml_ex(
+ s,
+ &value.0,
+ &ManifestYamlOptions {
+ padding: " ",
+ arr_element_padding: if indent_array_in_object.unwrap_or(false) {
+ " "
+ } else {
+ ""
+ },
+ quote_keys: quote_keys.unwrap_or(true),
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: preserve_order.unwrap_or(false),
+ },
+ )
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_reverse(value: ArrValue) -> Result<ArrValue> {
+ Ok(value.reversed())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_str_replace(str: String, from: IStr, to: IStr) -> Result<String> {
+ Ok(str.replace(&from as &str, &to as &str))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> Result<VecVal> {
+ use Either2::*;
+ Ok(VecVal(Cc::new(match maxsplits {
+ A(n) => str
+ .splitn(n + 1, &c as &str)
+ .map(|s| Val::Str(s.into()))
+ .collect(),
+ B(_) => str.split(&c as &str).map(|s| Val::Str(s.into())).collect(),
+ })))
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_ascii_upper(str: IStr) -> Result<String> {
+ Ok(str.to_ascii_uppercase())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_ascii_lower(str: IStr) -> Result<String> {
+ Ok(str.to_ascii_lowercase())
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_member(s: State, arr: IndexableVal, x: Any) -> Result<bool> {
+ match arr {
+ IndexableVal::Str(str) => {
+ let x: IStr = IStr::from_untyped(x.0, s)?;
+ Ok(!x.is_empty() && str.contains(&*x))
+ }
+ IndexableVal::Arr(a) => {
+ for item in a.iter(s.clone()) {
+ let item = item?;
+ if equals(s.clone(), &item, &x.0)? {
+ return Ok(true);
+ }
+ }
+ Ok(false)
+ }
+ }
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_count(s: State, arr: Vec<Any>, v: Any) -> Result<usize> {
+ let mut count = 0;
+ for item in &arr {
+ if equals(s.clone(), &item.0, &v.0)? {
+ count += 1;
+ }
+ }
+ Ok(count)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_any(s: State, arr: ArrValue) -> Result<bool> {
+ for v in arr.iter(s.clone()) {
+ let v = bool::from_untyped(v?, s.clone())?;
+ if v {
+ return Ok(true);
+ }
+ }
+ Ok(false)
+}
+
+#[jrsonnet_macros::builtin]
+fn builtin_all(s: State, arr: ArrValue) -> Result<bool> {
+ for v in arr.iter(s.clone()) {
+ let v = bool::from_untyped(v?, s.clone())?;
+ if !v {
+ return Ok(false);
+ }
+ }
+ Ok(true)
+}
crates/jrsonnet-evaluator/src/stdlib/sort.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/stdlib/sort.rs
@@ -0,0 +1,110 @@
+use gcmodule::{Cc, Trace};
+
+use crate::{
+ error::{Error, LocError, Result},
+ function::FuncVal,
+ throw,
+ typed::Any,
+ State, Val,
+};
+
+#[derive(Debug, Clone, thiserror::Error, Trace)]
+pub enum SortError {
+ #[error("sort key should be string or number")]
+ SortKeyShouldBeStringOrNumber,
+ #[error("sort elements should have equal types")]
+ SortElementsShouldHaveEqualType,
+}
+
+impl From<SortError> for LocError {
+ fn from(s: SortError) -> Self {
+ Self::new(Error::Sort(s))
+ }
+}
+
+#[derive(Copy, Clone)]
+enum SortKeyType {
+ Number,
+ String,
+ Unknown,
+}
+
+#[derive(PartialEq)]
+struct NonNaNf64(f64);
+impl PartialOrd for NonNaNf64 {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ self.0.partial_cmp(&other.0)
+ }
+}
+impl Eq for NonNaNf64 {}
+impl Ord for NonNaNf64 {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.partial_cmp(other).expect("non nan")
+ }
+}
+
+fn get_sort_type<T>(
+ values: &mut Vec<T>,
+ key_getter: impl Fn(&mut T) -> &mut Val,
+) -> Result<SortKeyType> {
+ let mut sort_type = SortKeyType::Unknown;
+ for i in values.iter_mut() {
+ let i = key_getter(i);
+ match (i, sort_type) {
+ (Val::Str(_), SortKeyType::Unknown) => sort_type = SortKeyType::String,
+ (Val::Num(_), SortKeyType::Unknown) => sort_type = SortKeyType::Number,
+ (Val::Str(_), SortKeyType::String) | (Val::Num(_), SortKeyType::Number) => {}
+ (Val::Str(_) | Val::Num(_), _) => {
+ throw!(SortError::SortElementsShouldHaveEqualType)
+ }
+ _ => throw!(SortError::SortKeyShouldBeStringOrNumber),
+ }
+ }
+ Ok(sort_type)
+}
+
+/// * `key_getter` - None, if identity sort required
+pub fn sort(s: State, values: Cc<Vec<Val>>, key_getter: FuncVal) -> Result<Cc<Vec<Val>>> {
+ if values.len() <= 1 {
+ return Ok(values);
+ }
+ if !key_getter.is_identity() {
+ // Slow path, user provided key getter
+ let mut vk = Vec::with_capacity(values.len());
+ for value in values.iter() {
+ vk.push((
+ value.clone(),
+ key_getter.evaluate_simple(s.clone(), &(Any(value.clone()),))?,
+ ));
+ }
+ let sort_type = get_sort_type(&mut vk, |v| &mut v.1)?;
+ match sort_type {
+ SortKeyType::Number => vk.sort_by_key(|v| match v.1 {
+ Val::Num(n) => NonNaNf64(n),
+ _ => unreachable!(),
+ }),
+ SortKeyType::String => vk.sort_by_key(|v| match &v.1 {
+ Val::Str(s) => s.clone(),
+ _ => unreachable!(),
+ }),
+ SortKeyType::Unknown => unreachable!(),
+ };
+ Ok(Cc::new(vk.into_iter().map(|v| v.0).collect()))
+ } else {
+ // Fast path, identity key getter
+ let mut values = (*values).clone();
+ let sort_type = get_sort_type(&mut values, |k| k)?;
+ match sort_type {
+ SortKeyType::Number => values.sort_unstable_by_key(|v| match v {
+ Val::Num(n) => NonNaNf64(*n),
+ _ => unreachable!(),
+ }),
+ SortKeyType::String => values.sort_unstable_by_key(|v| match v {
+ Val::Str(s) => s.clone(),
+ _ => unreachable!(),
+ }),
+ SortKeyType::Unknown => unreachable!(),
+ };
+ Ok(Cc::new(values))
+ }
+}
crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -7,9 +7,10 @@
use crate::{
error::{Error::*, Result},
+ function::{FuncDesc, FuncVal},
throw,
typed::CheckType,
- val::{ArrValue, FuncDesc, FuncVal, IndexableVal},
+ val::{ArrValue, IndexableVal},
ObjValue, ObjValueBuilder, State, Val,
};
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -2,22 +2,17 @@
use gcmodule::{Cc, Trace};
use jrsonnet_interner::IStr;
-use jrsonnet_parser::{LocExpr, ParamsDesc};
use jrsonnet_types::ValType;
use crate::{
- builtin::manifest::{
- manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, ManifestYamlOptions,
- },
cc_ptr_eq,
error::{Error::*, LocError},
- evaluate,
- function::{
- parse_default_function_call, parse_function_call, ArgsLike, Builtin, CallLocation,
- StaticBuiltin,
+ function::FuncVal,
+ gc::TraceBox,
+ stdlib::manifest::{
+ manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, ManifestYamlOptions,
},
- gc::TraceBox,
- throw, Context, ObjValue, Result, State,
+ throw, ObjValue, Result, State,
};
pub trait LazyValValue: Trace {
@@ -80,98 +75,6 @@
impl PartialEq for LazyVal {
fn eq(&self, other: &Self) -> bool {
cc_ptr_eq(&self.0, &other.0)
- }
-}
-
-#[derive(Debug, PartialEq, Trace)]
-pub struct FuncDesc {
- pub name: IStr,
- pub ctx: Context,
- pub params: ParamsDesc,
- pub body: LocExpr,
-}
-impl FuncDesc {
- /// Create body context, but fill arguments without defaults with lazy error
- pub fn default_body_context(&self) -> Context {
- parse_default_function_call(self.ctx.clone(), &self.params)
- }
-
- /// Create context, with which body code will run
- pub fn call_body_context(
- &self,
- s: State,
- call_ctx: Context,
- args: &dyn ArgsLike,
- tailstrict: bool,
- ) -> Result<Context> {
- parse_function_call(
- s,
- call_ctx,
- self.ctx.clone(),
- &self.params,
- args,
- tailstrict,
- )
- }
-}
-
-#[allow(clippy::module_name_repetitions)]
-#[derive(Trace, Clone)]
-pub enum FuncVal {
- /// Plain function implemented in jsonnet
- Normal(Cc<FuncDesc>),
- /// Standard library function
- StaticBuiltin(#[skip_trace] &'static dyn StaticBuiltin),
- /// User-provided function
- Builtin(Cc<TraceBox<dyn Builtin>>),
-}
-
-impl Debug for FuncVal {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),
- Self::StaticBuiltin(arg0) => {
- f.debug_tuple("StaticBuiltin").field(&arg0.name()).finish()
- }
- Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),
- }
- }
-}
-
-impl FuncVal {
- pub fn args_len(&self) -> usize {
- match self {
- Self::Normal(n) => n.params.iter().filter(|p| p.1.is_none()).count(),
- Self::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default).count(),
- Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default).count(),
- }
- }
- pub fn name(&self) -> IStr {
- match self {
- Self::Normal(normal) => normal.name.clone(),
- Self::StaticBuiltin(builtin) => builtin.name().into(),
- Self::Builtin(builtin) => builtin.name().into(),
- }
- }
- pub fn evaluate(
- &self,
- s: State,
- call_ctx: Context,
- loc: CallLocation,
- args: &dyn ArgsLike,
- tailstrict: bool,
- ) -> Result<Val> {
- match self {
- Self::Normal(func) => {
- let body_ctx = func.call_body_context(s.clone(), call_ctx, args, tailstrict)?;
- evaluate(s, body_ctx, &func.body)
- }
- Self::StaticBuiltin(b) => b.call(s, call_ctx, loc, args),
- Self::Builtin(b) => b.call(s, call_ctx, loc, args),
- }
- }
- pub fn evaluate_simple(&self, s: State, args: &dyn ArgsLike) -> Result<Val> {
- self.evaluate(s, Context::default(), CallLocation::native(), args, true)
}
}
crates/jrsonnet-evaluator/tests/as_native.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/as_native.rs
@@ -0,0 +1,21 @@
+use std::path::PathBuf;
+
+use jrsonnet_evaluator::{error::Result, State};
+
+mod common;
+
+#[test]
+fn as_native() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+
+ let val = s.evaluate_snippet_raw(PathBuf::new().into(), r#"function(a, b) a + b"#.into())?;
+ let func = val.as_func().expect("this is function");
+
+ let native = func.into_native::<((u32, u32), u32)>();
+
+ ensure_eq!(native(s.clone(), 1, 2)?, 3);
+ ensure_eq!(native(s, 3, 4)?, 7);
+
+ Ok(())
+}
crates/jrsonnet-evaluator/tests/builtin.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/builtin.rs
+++ b/crates/jrsonnet-evaluator/tests/builtin.rs
@@ -5,10 +5,9 @@
use gcmodule::Cc;
use jrsonnet_evaluator::{
error::Result,
- function::{builtin, Builtin, CallLocation},
+ function::{builtin, builtin::Builtin, CallLocation, FuncVal},
gc::TraceBox,
typed::Typed,
- val::FuncVal,
State, Val,
};
@@ -26,7 +25,7 @@
s.clone(),
s.create_default_context(),
CallLocation::native(),
- &[],
+ &(),
)?,
s.clone(),
)?;
crates/jrsonnet-evaluator/tests/common.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/common.rs
+++ b/crates/jrsonnet-evaluator/tests/common.rs
@@ -1,17 +1,16 @@
use jrsonnet_evaluator::{
- error::Result, function::builtin, throw_runtime, val::FuncVal, LazyVal, ObjValueBuilder, State,
- Val,
+ error::Result,
+ function::{builtin, FuncVal},
+ throw_runtime, LazyVal, ObjValueBuilder, State, Val,
};
#[macro_export]
macro_rules! ensure_eq {
($a:expr, $b:expr $(,)?) => {{
- if $a != $b {
- ::jrsonnet_evaluator::throw_runtime!(
- "assertion failed: a != b\na={:#?}\nb={:#?}",
- $a,
- $b,
- )
+ let a = &$a;
+ let b = &$b;
+ if a != b {
+ ::jrsonnet_evaluator::throw_runtime!("assertion failed: a != b\na={:#?}\nb={:#?}", a, b)
}
}};
}
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -349,7 +349,7 @@
const _: () = {
use ::jrsonnet_evaluator::{
State, Val,
- function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},
+ function::{builtin::{Builtin, StaticBuiltin, BuiltinParam}, CallLocation, ArgsLike, parse::parse_builtin_call},
error::Result, Context, typed::Typed,
parser::ExprLocation,
};
crates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -298,6 +298,8 @@
Function(ParamsDesc, LocExpr),
/// std.thisFile
IntrinsicThisFile,
+ /// std.id,
+ IntrinsicId,
/// std.primitiveEquals
Intrinsic(IStr),
/// if true == false then 1 else 2
crates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -211,6 +211,7 @@
= literal(s)
/ quiet!{"$intrinsicThisFile" {Expr::IntrinsicThisFile}}
+ / quiet!{"$intrinsicId" {Expr::IntrinsicId}}
/ quiet!{"$intrinsic(" name:$(id()) ")" {Expr::Intrinsic(name.into())}}
/ string_expr(s) / number_expr(s)
crates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -4,6 +4,7 @@
# Magic legacy field
thisFile:: $intrinsicThisFile,
+ id:: $intrinsicId,
# Those functions aren't normally located in stdlib
length:: $intrinsic(length),
@@ -24,7 +25,6 @@
decodeUTF8:: $intrinsic(decodeUTF8),
md5:: $intrinsic(md5),
trace:: $intrinsic(trace),
- id:: $intrinsic(id),
parseJson:: $intrinsic(parseJson),
parseYaml:: $intrinsic(parseYaml),