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.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/function/native.rs
@@ -0,0 +1,43 @@
+use super::{arglike::ArgLike, CallLocation, FuncVal};
+use crate::{error::Result, typed::Typed, State};
+
+pub trait NativeDesc {
+ type Value;
+ fn into_native(val: FuncVal) -> Self::Value;
+}
+macro_rules! impl_native_desc {
+ ($($gen:ident)*) => {
+ impl<$($gen,)* O> NativeDesc for (($($gen,)*), O)
+ where
+ $($gen: ArgLike,)*
+ O: Typed,
+ {
+ type Value = Box<dyn Fn(State, $($gen,)*) -> Result<O>>;
+
+ #[allow(non_snake_case)]
+ fn into_native(val: FuncVal) -> Self::Value {
+ Box::new(move |s: State, $($gen),*| {
+ let val = val.evaluate(
+ s.clone(),
+ s.create_default_context(),
+ CallLocation::native(),
+ &($($gen,)*),
+ true
+ )?;
+ O::from_untyped(val, s.clone())
+ })
+ }
+ }
+ };
+ ($($cur:ident)* @ $c:ident $($rest:ident)*) => {
+ impl_native_desc!($($cur)*);
+ impl_native_desc!($($cur)* $c @ $($rest)*);
+ };
+ ($($cur:ident)* @) => {
+ impl_native_desc!($($cur)*);
+ }
+}
+
+impl_native_desc! {
+ @ A B C D E F G H I J K L
+}
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.rsdiffbeforeafterboth1#![allow(clippy::redundant_closure_call)]23use std::{4 path::{Path, PathBuf},5 rc::Rc,6};78use peg::parser;9mod expr;10pub use expr::*;11pub use jrsonnet_interner::IStr;12pub use peg;13mod unescape;1415pub struct ParserSettings {16 pub file_name: Rc<Path>,17}1819macro_rules! expr_bin {20 ($a:ident $op:ident $b:ident) => {21 Expr::BinaryOp($a, $op, $b)22 };23}24macro_rules! expr_un {25 ($op:ident $a:ident) => {26 Expr::UnaryOp($op, $a)27 };28}2930parser! {31 grammar jsonnet_parser() for str {32 use peg::ParseLiteral;3334 rule eof() = quiet!{![_]} / expected!("<eof>")35 rule eol() = "\n" / eof()3637 /// Standard C-like comments38 rule comment()39 = "//" (!eol()[_])* eol()40 / "/*" ("\\*/" / "\\\\" / (!("*/")[_]))* "*/"41 / "#" (!eol()[_])* eol()4243 rule single_whitespace() = quiet!{([' ' | '\r' | '\n' | '\t'] / comment())} / expected!("<whitespace>")44 rule _() = quiet!{([' ' | '\r' | '\n' | '\t']+) / comment()}* / expected!("<whitespace>")4546 /// For comma-delimited elements47 rule comma() = quiet!{_ "," _} / expected!("<comma>")48 rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().next().unwrap()}49 rule digit() -> char = d:$(['0'..='9']) {d.chars().next().unwrap()}50 rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z']51 /// Sequence of digits52 rule uint_str() -> &'input str = a:$(digit()+) { a }53 /// Number in scientific notation format54 rule number() -> f64 = quiet!{a:$(uint_str() ("." uint_str())? (['e'|'E'] (s:['+'|'-'])? uint_str())?) {? a.parse().map_err(|_| "<number>") }} / expected!("<number>")5556 /// Reserved word followed by any non-alphanumberic57 rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "importbin" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident()58 rule id() = quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("<identifier>")5960 rule keyword(id: &'static str) -> ()61 = ##parse_string_literal(id) end_of_ident()6263 pub rule param(s: &ParserSettings) -> expr::Param = name:$(id()) expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name.into(), expr) }64 pub rule params(s: &ParserSettings) -> expr::ParamsDesc65 = params:param(s) ** comma() comma()? { expr::ParamsDesc(Rc::new(params)) }66 / { expr::ParamsDesc(Rc::new(Vec::new())) }6768 pub rule arg(s: &ParserSettings) -> (Option<IStr>, LocExpr)69 = quiet! { name:(s:$(id()) _ "=" _ {s})? expr:expr(s) {(name.map(Into::into), expr)} }70 / expected!("<argument>")7172 pub rule args(s: &ParserSettings) -> expr::ArgsDesc73 = args:arg(s)**comma() comma()? {?74 let unnamed_count = args.iter().take_while(|(n, _)| n.is_none()).count();75 let mut unnamed = Vec::with_capacity(unnamed_count);76 let mut named = Vec::with_capacity(args.len() - unnamed_count);77 let mut named_started = false;78 for (name, value) in args {79 if let Some(name) = name {80 named_started = true;81 named.push((name, value));82 } else {83 if named_started {84 return Err("<named argument>")85 }86 unnamed.push(value);87 }88 }89 Ok(expr::ArgsDesc::new(unnamed, named))90 }9192 pub rule bind(s: &ParserSettings) -> expr::BindSpec93 = name:$(id()) _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: None, value: expr}}94 / name:$(id()) _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: Some(params), value: expr}}95 pub rule assertion(s: &ParserSettings) -> expr::AssertStmt96 = keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }9798 pub rule whole_line() -> &'input str99 = str:$((!['\n'][_])* "\n") {str}100 pub rule string_block() -> String101 = "|||" (!['\n']single_whitespace())* "\n"102 empty_lines:$(['\n']*)103 prefix:[' ' | '\t']+ first_line:whole_line()104 lines:("\n" {"\n"} / [' ' | '\t']*<{prefix.len()}> s:whole_line() {s})*105 [' ' | '\t']*<, {prefix.len() - 1}> "|||"106 {let mut l = empty_lines.to_owned(); l.push_str(first_line); l.extend(lines); l}107108 rule hex_char()109 = quiet! { ['0'..='9' | 'a'..='f' | 'A'..='F'] } / expected!("<hex char>")110111 rule string_char(c: rule<()>)112 = (!['\\']!c()[_])+113 / "\\\\"114 / "\\u" hex_char() hex_char() hex_char() hex_char()115 / "\\x" hex_char() hex_char()116 / ['\\'] (quiet! { ['b' | 'f' | 'n' | 'r' | 't' | '"' | '\''] } / expected!("<escape character>"))117 pub rule string() -> String118 = ['"'] str:$(string_char(<"\"">)*) ['"'] {? unescape::unescape(str).ok_or("<escaped string>")}119 / ['\''] str:$(string_char(<"\'">)*) ['\''] {? unescape::unescape(str).ok_or("<escaped string>")}120 / quiet!{ "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")}121 / "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")}122 / string_block() } / expected!("<string>")123124 pub rule field_name(s: &ParserSettings) -> expr::FieldName125 = name:$(id()) {expr::FieldName::Fixed(name.into())}126 / name:string() {expr::FieldName::Fixed(name.into())}127 / "[" _ expr:expr(s) _ "]" {expr::FieldName::Dyn(expr)}128 pub rule visibility() -> expr::Visibility129 = ":::" {expr::Visibility::Unhide}130 / "::" {expr::Visibility::Hidden}131 / ":" {expr::Visibility::Normal}132 pub rule field(s: &ParserSettings) -> expr::FieldMember133 = name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {expr::FieldMember{134 name,135 plus: plus.is_some(),136 params: None,137 visibility,138 value,139 }}140 / name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {expr::FieldMember{141 name,142 plus: false,143 params: Some(params),144 visibility,145 value,146 }}147 pub rule obj_local(s: &ParserSettings) -> BindSpec148 = keyword("local") _ bind:bind(s) {bind}149 pub rule member(s: &ParserSettings) -> expr::Member150 = bind:obj_local(s) {expr::Member::BindStmt(bind)}151 / assertion:assertion(s) {expr::Member::AssertStmt(assertion)}152 / field:field(s) {expr::Member::Field(field)}153 pub rule objinside(s: &ParserSettings) -> expr::ObjBody154 = pre_locals:(b: obj_local(s) comma() {b})* "[" _ key:expr(s) _ "]" _ plus:"+"? _ ":" _ value:expr(s) post_locals:(comma() b:obj_local(s) {b})* _ forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {155 let mut compspecs = vec![CompSpec::ForSpec(forspec)];156 compspecs.extend(others.unwrap_or_default());157 expr::ObjBody::ObjComp(expr::ObjComp{158 pre_locals,159 key,160 plus: plus.is_some(),161 value,162 post_locals,163 compspecs,164 })165 }166 / members:(member(s) ** comma()) comma()? {expr::ObjBody::MemberList(members)}167 pub rule ifspec(s: &ParserSettings) -> IfSpecData168 = keyword("if") _ expr:expr(s) {IfSpecData(expr)}169 pub rule forspec(s: &ParserSettings) -> ForSpecData170 = keyword("for") _ id:$(id()) _ keyword("in") _ cond:expr(s) {ForSpecData(id.into(), cond)}171 pub rule compspec(s: &ParserSettings) -> Vec<expr::CompSpec>172 = s:(i:ifspec(s) { expr::CompSpec::IfSpec(i) } / f:forspec(s) {expr::CompSpec::ForSpec(f)} ) ** _ {s}173 pub rule local_expr(s: &ParserSettings) -> Expr174 = keyword("local") _ binds:bind(s) ** comma() _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, expr) }175 pub rule string_expr(s: &ParserSettings) -> Expr176 = s:string() {Expr::Str(s.into())}177 pub rule obj_expr(s: &ParserSettings) -> Expr178 = "{" _ body:objinside(s) _ "}" {Expr::Obj(body)}179 pub rule array_expr(s: &ParserSettings) -> Expr180 = "[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(elems)}181 pub rule array_comp_expr(s: &ParserSettings) -> Expr182 = "[" _ expr:expr(s) _ comma()? _ forspec:forspec(s) _ others:(others: compspec(s) _ {others})? "]" {183 let mut specs = vec![CompSpec::ForSpec(forspec)];184 specs.extend(others.unwrap_or_default());185 Expr::ArrComp(expr, specs)186 }187 pub rule number_expr(s: &ParserSettings) -> Expr188 = n:number() { expr::Expr::Num(n) }189 pub rule var_expr(s: &ParserSettings) -> Expr190 = n:$(id()) { expr::Expr::Var(n.into()) }191 pub rule id_loc(s: &ParserSettings) -> LocExpr192 = a:position!() n:$(id()) b:position!() { LocExpr(Rc::new(expr::Expr::Str(n.into())), ExprLocation(s.file_name.clone(), a,b)) }193 pub rule if_then_else_expr(s: &ParserSettings) -> Expr194 = cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{195 cond,196 cond_then,197 cond_else,198 }}199200 pub rule literal(s: &ParserSettings) -> Expr201 = v:(202 keyword("null") {LiteralType::Null}203 / keyword("true") {LiteralType::True}204 / keyword("false") {LiteralType::False}205 / keyword("self") {LiteralType::This}206 / keyword("$") {LiteralType::Dollar}207 / keyword("super") {LiteralType::Super}208 ) {Expr::Literal(v)}209210 pub rule expr_basic(s: &ParserSettings) -> Expr211 = literal(s)212213 / quiet!{"$intrinsicThisFile" {Expr::IntrinsicThisFile}}214 / quiet!{"$intrinsic(" name:$(id()) ")" {Expr::Intrinsic(name.into())}}215216 / string_expr(s) / number_expr(s)217 / array_expr(s)218 / obj_expr(s)219 / array_expr(s)220 / array_comp_expr(s)221222 / keyword("importstr") _ path:string() {Expr::ImportStr(PathBuf::from(path))}223 / keyword("importbin") _ path:string() {Expr::ImportBin(PathBuf::from(path))}224 / keyword("import") _ path:string() {Expr::Import(PathBuf::from(path))}225226 / var_expr(s)227 / local_expr(s)228 / if_then_else_expr(s)229230 / keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, expr)}231 / assertion:assertion(s) _ ";" _ expr:expr(s) { Expr::AssertExpr(assertion, expr) }232233 / keyword("error") _ expr:expr(s) { Expr::ErrorStmt(expr) }234235 rule slice_part(s: &ParserSettings) -> Option<LocExpr>236 = _ e:(e:expr(s) _{e})? {e}237 pub rule slice_desc(s: &ParserSettings) -> SliceDesc238 = start:slice_part(s) ":" pair:(end:slice_part(s) step:(":" e:slice_part(s){e})? {(end, step.flatten())})? {239 let (end, step) = if let Some((end, step)) = pair {240 (end, step)241 }else{242 (None, None)243 };244245 SliceDesc { start, end, step }246 }247248 rule binop(x: rule<()>) -> ()249 = quiet!{ x() } / expected!("<binary op>")250 rule unaryop(x: rule<()>) -> ()251 = quiet!{ x() } / expected!("<unary op>")252253254 use BinaryOpType::*;255 use UnaryOpType::*;256 rule expr(s: &ParserSettings) -> LocExpr257 = precedence! {258 start:position!() v:@ end:position!() { LocExpr(Rc::new(v), ExprLocation(s.file_name.clone(), start, end)) }259 --260 a:(@) _ binop(<"||">) _ b:@ {expr_bin!(a Or b)}261 --262 a:(@) _ binop(<"&&">) _ b:@ {expr_bin!(a And b)}263 --264 a:(@) _ binop(<"|">) _ b:@ {expr_bin!(a BitOr b)}265 --266 a:@ _ binop(<"^">) _ b:(@) {expr_bin!(a BitXor b)}267 --268 a:(@) _ binop(<"&">) _ b:@ {expr_bin!(a BitAnd b)}269 --270 a:(@) _ binop(<"==">) _ b:@ {expr_bin!(a Eq b)}271 a:(@) _ binop(<"!=">) _ b:@ {expr_bin!(a Neq b)}272 --273 a:(@) _ binop(<"<">) _ b:@ {expr_bin!(a Lt b)}274 a:(@) _ binop(<">">) _ b:@ {expr_bin!(a Gt b)}275 a:(@) _ binop(<"<=">) _ b:@ {expr_bin!(a Lte b)}276 a:(@) _ binop(<">=">) _ b:@ {expr_bin!(a Gte b)}277 a:(@) _ binop(<keyword("in")>) _ b:@ {expr_bin!(a In b)}278 --279 a:(@) _ binop(<"<<">) _ b:@ {expr_bin!(a Lhs b)}280 a:(@) _ binop(<">>">) _ b:@ {expr_bin!(a Rhs b)}281 --282 a:(@) _ binop(<"+">) _ b:@ {expr_bin!(a Add b)}283 a:(@) _ binop(<"-">) _ b:@ {expr_bin!(a Sub b)}284 --285 a:(@) _ binop(<"*">) _ b:@ {expr_bin!(a Mul b)}286 a:(@) _ binop(<"/">) _ b:@ {expr_bin!(a Div b)}287 a:(@) _ binop(<"%">) _ b:@ {expr_bin!(a Mod b)}288 --289 unaryop(<"-">) _ b:@ {expr_un!(Minus b)}290 unaryop(<"!">) _ b:@ {expr_un!(Not b)}291 unaryop(<"~">) _ b:@ {expr_un!(BitNot b)}292 --293 a:(@) _ "[" _ e:slice_desc(s) _ "]" {Expr::Slice(a, e)}294 a:(@) _ "." _ a:position!() e:id_loc(s) b:position!() {Expr::Index(a, e)}295 a:(@) _ "[" _ e:expr(s) _ "]" {Expr::Index(a, e)}296 a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(a, args, ts.is_some())}297 a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(a, body)}298 --299 e:expr_basic(s) {e}300 "(" _ e:expr(s) _ ")" {Expr::Parened(e)}301 }302303 pub rule jsonnet(s: &ParserSettings) -> LocExpr = _ e:expr(s) _ {e}304 }305}306307pub type ParseError = peg::error::ParseError<peg::str::LineCol>;308pub fn parse(str: &str, settings: &ParserSettings) -> Result<LocExpr, ParseError> {309 jsonnet_parser::jsonnet(str, settings)310}311/// Used for importstr values312pub fn string_to_expr(str: IStr, settings: &ParserSettings) -> LocExpr {313 let len = str.len();314 LocExpr(315 Rc::new(Expr::Str(str)),316 ExprLocation(settings.file_name.clone(), 0, len),317 )318}319320#[cfg(test)]321pub mod tests {322 use std::path::PathBuf;323324 use BinaryOpType::*;325326 use super::{expr::*, parse};327 use crate::ParserSettings;328329 macro_rules! parse {330 ($s:expr) => {331 parse(332 $s,333 &ParserSettings {334 file_name: PathBuf::from("test.jsonnet").into(),335 },336 )337 .unwrap()338 };339 }340341 macro_rules! el {342 ($expr:expr, $from:expr, $to:expr$(,)?) => {343 LocExpr(344 std::rc::Rc::new($expr),345 ExprLocation(PathBuf::from("test.jsonnet").into(), $from, $to),346 )347 };348 }349350 #[test]351 fn multiline_string() {352 assert_eq!(353 parse!("|||\n Hello world!\n a\n|||"),354 el!(Expr::Str("Hello world!\n a\n".into()), 0, 31),355 );356 assert_eq!(357 parse!("|||\n Hello world!\n a\n|||"),358 el!(Expr::Str("Hello world!\n a\n".into()), 0, 27),359 );360 assert_eq!(361 parse!("|||\n\t\tHello world!\n\t\t\ta\n|||"),362 el!(Expr::Str("Hello world!\n\ta\n".into()), 0, 27),363 );364 assert_eq!(365 parse!("|||\n Hello world!\n a\n |||"),366 el!(Expr::Str("Hello world!\n a\n".into()), 0, 30),367 );368 }369370 #[test]371 fn slice() {372 parse!("a[1:]");373 parse!("a[1::]");374 parse!("a[:1:]");375 parse!("a[::1]");376 parse!("str[:len - 1]");377 }378379 #[test]380 fn string_escaping() {381 assert_eq!(382 parse!(r#""Hello, \"world\"!""#),383 el!(Expr::Str(r#"Hello, "world"!"#.into()), 0, 19),384 );385 assert_eq!(386 parse!(r#"'Hello \'world\'!'"#),387 el!(Expr::Str("Hello 'world'!".into()), 0, 18),388 );389 assert_eq!(parse!(r#"'\\\\'"#), el!(Expr::Str("\\\\".into()), 0, 6));390 }391392 #[test]393 fn string_unescaping() {394 assert_eq!(395 parse!(r#""Hello\nWorld""#),396 el!(Expr::Str("Hello\nWorld".into()), 0, 14),397 );398 }399400 #[test]401 fn string_verbantim() {402 assert_eq!(403 parse!(r#"@"Hello\n""World""""#),404 el!(Expr::Str("Hello\\n\"World\"".into()), 0, 19),405 );406 }407408 #[test]409 fn imports() {410 assert_eq!(411 parse!("import \"hello\""),412 el!(Expr::Import(PathBuf::from("hello")), 0, 14),413 );414 assert_eq!(415 parse!("importstr \"garnish.txt\""),416 el!(Expr::ImportStr(PathBuf::from("garnish.txt")), 0, 23)417 );418 }419420 #[test]421 fn empty_object() {422 assert_eq!(423 parse!("{}"),424 el!(Expr::Obj(ObjBody::MemberList(vec![])), 0, 2)425 );426 }427428 #[test]429 fn basic_math() {430 assert_eq!(431 parse!("2+2*2"),432 el!(433 Expr::BinaryOp(434 el!(Expr::Num(2.0), 0, 1),435 Add,436 el!(437 Expr::BinaryOp(el!(Expr::Num(2.0), 2, 3), Mul, el!(Expr::Num(2.0), 4, 5)),438 2,439 5440 )441 ),442 0,443 5444 )445 );446 }447448 #[test]449 fn basic_math_with_indents() {450 assert_eq!(451 parse!("2 + 2 * 2 "),452 el!(453 Expr::BinaryOp(454 el!(Expr::Num(2.0), 0, 1),455 Add,456 el!(457 Expr::BinaryOp(el!(Expr::Num(2.0), 7, 8), Mul, el!(Expr::Num(2.0), 13, 14),),458 7,459 14460 ),461 ),462 0,463 14464 )465 );466 }467468 #[test]469 fn basic_math_parened() {470 assert_eq!(471 parse!("2+(2+2*2)"),472 el!(473 Expr::BinaryOp(474 el!(Expr::Num(2.0), 0, 1),475 Add,476 el!(477 Expr::Parened(el!(478 Expr::BinaryOp(479 el!(Expr::Num(2.0), 3, 4),480 Add,481 el!(482 Expr::BinaryOp(483 el!(Expr::Num(2.0), 5, 6),484 Mul,485 el!(Expr::Num(2.0), 7, 8),486 ),487 5,488 8489 ),490 ),491 3,492 8493 )),494 2,495 9496 ),497 ),498 0,499 9500 )501 );502 }503504 /// Comments should not affect parsing505 #[test]506 fn comments() {507 assert_eq!(508 parse!("2//comment\n+//comment\n3/*test*/*/*test*/4"),509 el!(510 Expr::BinaryOp(511 el!(Expr::Num(2.0), 0, 1),512 Add,513 el!(514 Expr::BinaryOp(515 el!(Expr::Num(3.0), 22, 23),516 Mul,517 el!(Expr::Num(4.0), 40, 41)518 ),519 22,520 41521 )522 ),523 0,524 41525 )526 );527 }528529 /// Comments should be able to be escaped530 #[test]531 fn comment_escaping() {532 assert_eq!(533 parse!("2/*\\*/+*/ - 22"),534 el!(535 Expr::BinaryOp(el!(Expr::Num(2.0), 0, 1), Sub, el!(Expr::Num(22.0), 12, 14)),536 0,537 14538 )539 );540 }541542 #[test]543 fn suffix() {544 // assert_eq!(parse!("std.test"), el!(Expr::Num(2.2)));545 // assert_eq!(parse!("std(2)"), el!(Expr::Num(2.2)));546 // assert_eq!(parse!("std.test(2)"), el!(Expr::Num(2.2)));547 // assert_eq!(parse!("a[b]"), el!(Expr::Num(2.2)))548 }549550 #[test]551 fn array_comp() {552 use Expr::*;553 /*554 `ArrComp(Apply(Index(Var("std") from "test.jsonnet":1-4, Var("deepJoin") from "test.jsonnet":5-13) from "test.jsonnet":1-13, ArgsDesc { unnamed: [Var("x") from "test.jsonnet":14-15], named: [] }, false) from "test.jsonnet":1-16, [ForSpec(ForSpecData("x", Var("arr") from "test.jsonnet":26-29))]) from "test.jsonnet":0-30`,555 `ArrComp(Apply(Index(Var("std") from "test.jsonnet":1-4, Str("deepJoin") from "test.jsonnet":5-13) from "test.jsonnet":1-13, ArgsDesc { unnamed: [Var("x") from "test.jsonnet":14-15], named: [] }, false) from "test.jsonnet":1-16, [ForSpec(ForSpecData("x", Var("arr") from "test.jsonnet":26-29))]) from "test.jsonnet":0-30`556 */557 assert_eq!(558 parse!("[std.deepJoin(x) for x in arr]"),559 el!(560 ArrComp(561 el!(562 Apply(563 el!(564 Index(565 el!(Var("std".into()), 1, 4),566 el!(Str("deepJoin".into()), 5, 13)567 ),568 1,569 13570 ),571 ArgsDesc::new(vec![el!(Var("x".into()), 14, 15)], vec![]),572 false,573 ),574 1,575 16576 ),577 vec![CompSpec::ForSpec(ForSpecData(578 "x".into(),579 el!(Var("arr".into()), 26, 29)580 ))]581 ),582 0,583 30584 ),585 )586 }587588 #[test]589 fn reserved() {590 use Expr::*;591 assert_eq!(parse!("null"), el!(Literal(LiteralType::Null), 0, 4));592 assert_eq!(parse!("nulla"), el!(Var("nulla".into()), 0, 5));593 }594595 #[test]596 fn multiple_args_buf() {597 parse!("a(b, null_fields)");598 }599600 #[test]601 fn infix_precedence() {602 use Expr::*;603 assert_eq!(604 parse!("!a && !b"),605 el!(606 BinaryOp(607 el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into()), 1, 2)), 0, 2),608 And,609 el!(UnaryOp(UnaryOpType::Not, el!(Var("b".into()), 7, 8)), 6, 8)610 ),611 0,612 8613 )614 );615 }616617 #[test]618 fn infix_precedence_division() {619 use Expr::*;620 assert_eq!(621 parse!("!a / !b"),622 el!(623 BinaryOp(624 el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into()), 1, 2)), 0, 2),625 Div,626 el!(UnaryOp(UnaryOpType::Not, el!(Var("b".into()), 6, 7)), 5, 7)627 ),628 0,629 7630 )631 );632 }633634 #[test]635 fn double_negation() {636 use Expr::*;637 assert_eq!(638 parse!("!!a"),639 el!(640 UnaryOp(641 UnaryOpType::Not,642 el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into()), 2, 3)), 1, 3)643 ),644 0,645 3646 )647 )648 }649650 #[test]651 fn array_test_error() {652 parse!("[a for a in b if c for e in f]");653 // ^^^^ failed code654 }655656 #[test]657 fn missing_newline_between_comment_and_eof() {658 parse!(659 "{a:1}660661 //+213"662 );663 }664665 #[test]666 fn default_param_before_nondefault() {667 parse!("local x(foo = 'foo', bar) = null; null");668 }669670 #[test]671 fn can_parse_stdlib() {672 parse!(jrsonnet_stdlib::STDLIB_STR);673 }674675 #[test]676 fn add_location_info_to_all_sub_expressions() {677 use Expr::*;678679 let file_name: std::rc::Rc<std::path::Path> = PathBuf::from("test.jsonnet").into();680 let expr = parse(681 "{} { local x = 1, x: x } + {}",682 &ParserSettings {683 file_name: file_name.clone(),684 },685 )686 .unwrap();687 assert_eq!(688 expr,689 el!(690 BinaryOp(691 el!(692 ObjExtend(693 el!(Obj(ObjBody::MemberList(vec![])), 0, 2),694 ObjBody::MemberList(vec![695 Member::BindStmt(BindSpec {696 name: "x".into(),697 params: None,698 value: el!(Num(1.0), 15, 16)699 }),700 Member::Field(FieldMember {701 name: FieldName::Fixed("x".into()),702 plus: false,703 params: None,704 visibility: Visibility::Normal,705 value: el!(Var("x".into()), 21, 22),706 })707 ])708 ),709 0,710 24711 ),712 BinaryOpType::Add,713 el!(Obj(ObjBody::MemberList(vec![])), 27, 29),714 ),715 0,716 29717 ),718 );719 }720 // From source code721 /*722 #[bench]723 fn bench_parse_peg(b: &mut Bencher) {724 b.iter(|| parse!(jrsonnet_stdlib::STDLIB_STR))725 }726 */727}1#![allow(clippy::redundant_closure_call)]23use std::{4 path::{Path, PathBuf},5 rc::Rc,6};78use peg::parser;9mod expr;10pub use expr::*;11pub use jrsonnet_interner::IStr;12pub use peg;13mod unescape;1415pub struct ParserSettings {16 pub file_name: Rc<Path>,17}1819macro_rules! expr_bin {20 ($a:ident $op:ident $b:ident) => {21 Expr::BinaryOp($a, $op, $b)22 };23}24macro_rules! expr_un {25 ($op:ident $a:ident) => {26 Expr::UnaryOp($op, $a)27 };28}2930parser! {31 grammar jsonnet_parser() for str {32 use peg::ParseLiteral;3334 rule eof() = quiet!{![_]} / expected!("<eof>")35 rule eol() = "\n" / eof()3637 /// Standard C-like comments38 rule comment()39 = "//" (!eol()[_])* eol()40 / "/*" ("\\*/" / "\\\\" / (!("*/")[_]))* "*/"41 / "#" (!eol()[_])* eol()4243 rule single_whitespace() = quiet!{([' ' | '\r' | '\n' | '\t'] / comment())} / expected!("<whitespace>")44 rule _() = quiet!{([' ' | '\r' | '\n' | '\t']+) / comment()}* / expected!("<whitespace>")4546 /// For comma-delimited elements47 rule comma() = quiet!{_ "," _} / expected!("<comma>")48 rule alpha() -> char = c:$(['_' | 'a'..='z' | 'A'..='Z']) {c.chars().next().unwrap()}49 rule digit() -> char = d:$(['0'..='9']) {d.chars().next().unwrap()}50 rule end_of_ident() = !['0'..='9' | '_' | 'a'..='z' | 'A'..='Z']51 /// Sequence of digits52 rule uint_str() -> &'input str = a:$(digit()+) { a }53 /// Number in scientific notation format54 rule number() -> f64 = quiet!{a:$(uint_str() ("." uint_str())? (['e'|'E'] (s:['+'|'-'])? uint_str())?) {? a.parse().map_err(|_| "<number>") }} / expected!("<number>")5556 /// Reserved word followed by any non-alphanumberic57 rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "importbin" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident()58 rule id() = quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("<identifier>")5960 rule keyword(id: &'static str) -> ()61 = ##parse_string_literal(id) end_of_ident()6263 pub rule param(s: &ParserSettings) -> expr::Param = name:$(id()) expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name.into(), expr) }64 pub rule params(s: &ParserSettings) -> expr::ParamsDesc65 = params:param(s) ** comma() comma()? { expr::ParamsDesc(Rc::new(params)) }66 / { expr::ParamsDesc(Rc::new(Vec::new())) }6768 pub rule arg(s: &ParserSettings) -> (Option<IStr>, LocExpr)69 = quiet! { name:(s:$(id()) _ "=" _ {s})? expr:expr(s) {(name.map(Into::into), expr)} }70 / expected!("<argument>")7172 pub rule args(s: &ParserSettings) -> expr::ArgsDesc73 = args:arg(s)**comma() comma()? {?74 let unnamed_count = args.iter().take_while(|(n, _)| n.is_none()).count();75 let mut unnamed = Vec::with_capacity(unnamed_count);76 let mut named = Vec::with_capacity(args.len() - unnamed_count);77 let mut named_started = false;78 for (name, value) in args {79 if let Some(name) = name {80 named_started = true;81 named.push((name, value));82 } else {83 if named_started {84 return Err("<named argument>")85 }86 unnamed.push(value);87 }88 }89 Ok(expr::ArgsDesc::new(unnamed, named))90 }9192 pub rule bind(s: &ParserSettings) -> expr::BindSpec93 = name:$(id()) _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: None, value: expr}}94 / name:$(id()) _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: Some(params), value: expr}}95 pub rule assertion(s: &ParserSettings) -> expr::AssertStmt96 = keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }9798 pub rule whole_line() -> &'input str99 = str:$((!['\n'][_])* "\n") {str}100 pub rule string_block() -> String101 = "|||" (!['\n']single_whitespace())* "\n"102 empty_lines:$(['\n']*)103 prefix:[' ' | '\t']+ first_line:whole_line()104 lines:("\n" {"\n"} / [' ' | '\t']*<{prefix.len()}> s:whole_line() {s})*105 [' ' | '\t']*<, {prefix.len() - 1}> "|||"106 {let mut l = empty_lines.to_owned(); l.push_str(first_line); l.extend(lines); l}107108 rule hex_char()109 = quiet! { ['0'..='9' | 'a'..='f' | 'A'..='F'] } / expected!("<hex char>")110111 rule string_char(c: rule<()>)112 = (!['\\']!c()[_])+113 / "\\\\"114 / "\\u" hex_char() hex_char() hex_char() hex_char()115 / "\\x" hex_char() hex_char()116 / ['\\'] (quiet! { ['b' | 'f' | 'n' | 'r' | 't' | '"' | '\''] } / expected!("<escape character>"))117 pub rule string() -> String118 = ['"'] str:$(string_char(<"\"">)*) ['"'] {? unescape::unescape(str).ok_or("<escaped string>")}119 / ['\''] str:$(string_char(<"\'">)*) ['\''] {? unescape::unescape(str).ok_or("<escaped string>")}120 / quiet!{ "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")}121 / "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")}122 / string_block() } / expected!("<string>")123124 pub rule field_name(s: &ParserSettings) -> expr::FieldName125 = name:$(id()) {expr::FieldName::Fixed(name.into())}126 / name:string() {expr::FieldName::Fixed(name.into())}127 / "[" _ expr:expr(s) _ "]" {expr::FieldName::Dyn(expr)}128 pub rule visibility() -> expr::Visibility129 = ":::" {expr::Visibility::Unhide}130 / "::" {expr::Visibility::Hidden}131 / ":" {expr::Visibility::Normal}132 pub rule field(s: &ParserSettings) -> expr::FieldMember133 = name:field_name(s) _ plus:"+"? _ visibility:visibility() _ value:expr(s) {expr::FieldMember{134 name,135 plus: plus.is_some(),136 params: None,137 visibility,138 value,139 }}140 / name:field_name(s) _ "(" _ params:params(s) _ ")" _ visibility:visibility() _ value:expr(s) {expr::FieldMember{141 name,142 plus: false,143 params: Some(params),144 visibility,145 value,146 }}147 pub rule obj_local(s: &ParserSettings) -> BindSpec148 = keyword("local") _ bind:bind(s) {bind}149 pub rule member(s: &ParserSettings) -> expr::Member150 = bind:obj_local(s) {expr::Member::BindStmt(bind)}151 / assertion:assertion(s) {expr::Member::AssertStmt(assertion)}152 / field:field(s) {expr::Member::Field(field)}153 pub rule objinside(s: &ParserSettings) -> expr::ObjBody154 = pre_locals:(b: obj_local(s) comma() {b})* "[" _ key:expr(s) _ "]" _ plus:"+"? _ ":" _ value:expr(s) post_locals:(comma() b:obj_local(s) {b})* _ forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {155 let mut compspecs = vec![CompSpec::ForSpec(forspec)];156 compspecs.extend(others.unwrap_or_default());157 expr::ObjBody::ObjComp(expr::ObjComp{158 pre_locals,159 key,160 plus: plus.is_some(),161 value,162 post_locals,163 compspecs,164 })165 }166 / members:(member(s) ** comma()) comma()? {expr::ObjBody::MemberList(members)}167 pub rule ifspec(s: &ParserSettings) -> IfSpecData168 = keyword("if") _ expr:expr(s) {IfSpecData(expr)}169 pub rule forspec(s: &ParserSettings) -> ForSpecData170 = keyword("for") _ id:$(id()) _ keyword("in") _ cond:expr(s) {ForSpecData(id.into(), cond)}171 pub rule compspec(s: &ParserSettings) -> Vec<expr::CompSpec>172 = s:(i:ifspec(s) { expr::CompSpec::IfSpec(i) } / f:forspec(s) {expr::CompSpec::ForSpec(f)} ) ** _ {s}173 pub rule local_expr(s: &ParserSettings) -> Expr174 = keyword("local") _ binds:bind(s) ** comma() _ ";" _ expr:expr(s) { Expr::LocalExpr(binds, expr) }175 pub rule string_expr(s: &ParserSettings) -> Expr176 = s:string() {Expr::Str(s.into())}177 pub rule obj_expr(s: &ParserSettings) -> Expr178 = "{" _ body:objinside(s) _ "}" {Expr::Obj(body)}179 pub rule array_expr(s: &ParserSettings) -> Expr180 = "[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(elems)}181 pub rule array_comp_expr(s: &ParserSettings) -> Expr182 = "[" _ expr:expr(s) _ comma()? _ forspec:forspec(s) _ others:(others: compspec(s) _ {others})? "]" {183 let mut specs = vec![CompSpec::ForSpec(forspec)];184 specs.extend(others.unwrap_or_default());185 Expr::ArrComp(expr, specs)186 }187 pub rule number_expr(s: &ParserSettings) -> Expr188 = n:number() { expr::Expr::Num(n) }189 pub rule var_expr(s: &ParserSettings) -> Expr190 = n:$(id()) { expr::Expr::Var(n.into()) }191 pub rule id_loc(s: &ParserSettings) -> LocExpr192 = a:position!() n:$(id()) b:position!() { LocExpr(Rc::new(expr::Expr::Str(n.into())), ExprLocation(s.file_name.clone(), a,b)) }193 pub rule if_then_else_expr(s: &ParserSettings) -> Expr194 = cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{195 cond,196 cond_then,197 cond_else,198 }}199200 pub rule literal(s: &ParserSettings) -> Expr201 = v:(202 keyword("null") {LiteralType::Null}203 / keyword("true") {LiteralType::True}204 / keyword("false") {LiteralType::False}205 / keyword("self") {LiteralType::This}206 / keyword("$") {LiteralType::Dollar}207 / keyword("super") {LiteralType::Super}208 ) {Expr::Literal(v)}209210 pub rule expr_basic(s: &ParserSettings) -> Expr211 = literal(s)212213 / quiet!{"$intrinsicThisFile" {Expr::IntrinsicThisFile}}214 / quiet!{"$intrinsicId" {Expr::IntrinsicId}}215 / quiet!{"$intrinsic(" name:$(id()) ")" {Expr::Intrinsic(name.into())}}216217 / string_expr(s) / number_expr(s)218 / array_expr(s)219 / obj_expr(s)220 / array_expr(s)221 / array_comp_expr(s)222223 / keyword("importstr") _ path:string() {Expr::ImportStr(PathBuf::from(path))}224 / keyword("importbin") _ path:string() {Expr::ImportBin(PathBuf::from(path))}225 / keyword("import") _ path:string() {Expr::Import(PathBuf::from(path))}226227 / var_expr(s)228 / local_expr(s)229 / if_then_else_expr(s)230231 / keyword("function") _ "(" _ params:params(s) _ ")" _ expr:expr(s) {Expr::Function(params, expr)}232 / assertion:assertion(s) _ ";" _ expr:expr(s) { Expr::AssertExpr(assertion, expr) }233234 / keyword("error") _ expr:expr(s) { Expr::ErrorStmt(expr) }235236 rule slice_part(s: &ParserSettings) -> Option<LocExpr>237 = _ e:(e:expr(s) _{e})? {e}238 pub rule slice_desc(s: &ParserSettings) -> SliceDesc239 = start:slice_part(s) ":" pair:(end:slice_part(s) step:(":" e:slice_part(s){e})? {(end, step.flatten())})? {240 let (end, step) = if let Some((end, step)) = pair {241 (end, step)242 }else{243 (None, None)244 };245246 SliceDesc { start, end, step }247 }248249 rule binop(x: rule<()>) -> ()250 = quiet!{ x() } / expected!("<binary op>")251 rule unaryop(x: rule<()>) -> ()252 = quiet!{ x() } / expected!("<unary op>")253254255 use BinaryOpType::*;256 use UnaryOpType::*;257 rule expr(s: &ParserSettings) -> LocExpr258 = precedence! {259 start:position!() v:@ end:position!() { LocExpr(Rc::new(v), ExprLocation(s.file_name.clone(), start, end)) }260 --261 a:(@) _ binop(<"||">) _ b:@ {expr_bin!(a Or b)}262 --263 a:(@) _ binop(<"&&">) _ b:@ {expr_bin!(a And b)}264 --265 a:(@) _ binop(<"|">) _ b:@ {expr_bin!(a BitOr b)}266 --267 a:@ _ binop(<"^">) _ b:(@) {expr_bin!(a BitXor b)}268 --269 a:(@) _ binop(<"&">) _ b:@ {expr_bin!(a BitAnd b)}270 --271 a:(@) _ binop(<"==">) _ b:@ {expr_bin!(a Eq b)}272 a:(@) _ binop(<"!=">) _ b:@ {expr_bin!(a Neq b)}273 --274 a:(@) _ binop(<"<">) _ b:@ {expr_bin!(a Lt b)}275 a:(@) _ binop(<">">) _ b:@ {expr_bin!(a Gt b)}276 a:(@) _ binop(<"<=">) _ b:@ {expr_bin!(a Lte b)}277 a:(@) _ binop(<">=">) _ b:@ {expr_bin!(a Gte b)}278 a:(@) _ binop(<keyword("in")>) _ b:@ {expr_bin!(a In b)}279 --280 a:(@) _ binop(<"<<">) _ b:@ {expr_bin!(a Lhs b)}281 a:(@) _ binop(<">>">) _ b:@ {expr_bin!(a Rhs b)}282 --283 a:(@) _ binop(<"+">) _ b:@ {expr_bin!(a Add b)}284 a:(@) _ binop(<"-">) _ b:@ {expr_bin!(a Sub b)}285 --286 a:(@) _ binop(<"*">) _ b:@ {expr_bin!(a Mul b)}287 a:(@) _ binop(<"/">) _ b:@ {expr_bin!(a Div b)}288 a:(@) _ binop(<"%">) _ b:@ {expr_bin!(a Mod b)}289 --290 unaryop(<"-">) _ b:@ {expr_un!(Minus b)}291 unaryop(<"!">) _ b:@ {expr_un!(Not b)}292 unaryop(<"~">) _ b:@ {expr_un!(BitNot b)}293 --294 a:(@) _ "[" _ e:slice_desc(s) _ "]" {Expr::Slice(a, e)}295 a:(@) _ "." _ a:position!() e:id_loc(s) b:position!() {Expr::Index(a, e)}296 a:(@) _ "[" _ e:expr(s) _ "]" {Expr::Index(a, e)}297 a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(a, args, ts.is_some())}298 a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(a, body)}299 --300 e:expr_basic(s) {e}301 "(" _ e:expr(s) _ ")" {Expr::Parened(e)}302 }303304 pub rule jsonnet(s: &ParserSettings) -> LocExpr = _ e:expr(s) _ {e}305 }306}307308pub type ParseError = peg::error::ParseError<peg::str::LineCol>;309pub fn parse(str: &str, settings: &ParserSettings) -> Result<LocExpr, ParseError> {310 jsonnet_parser::jsonnet(str, settings)311}312/// Used for importstr values313pub fn string_to_expr(str: IStr, settings: &ParserSettings) -> LocExpr {314 let len = str.len();315 LocExpr(316 Rc::new(Expr::Str(str)),317 ExprLocation(settings.file_name.clone(), 0, len),318 )319}320321#[cfg(test)]322pub mod tests {323 use std::path::PathBuf;324325 use BinaryOpType::*;326327 use super::{expr::*, parse};328 use crate::ParserSettings;329330 macro_rules! parse {331 ($s:expr) => {332 parse(333 $s,334 &ParserSettings {335 file_name: PathBuf::from("test.jsonnet").into(),336 },337 )338 .unwrap()339 };340 }341342 macro_rules! el {343 ($expr:expr, $from:expr, $to:expr$(,)?) => {344 LocExpr(345 std::rc::Rc::new($expr),346 ExprLocation(PathBuf::from("test.jsonnet").into(), $from, $to),347 )348 };349 }350351 #[test]352 fn multiline_string() {353 assert_eq!(354 parse!("|||\n Hello world!\n a\n|||"),355 el!(Expr::Str("Hello world!\n a\n".into()), 0, 31),356 );357 assert_eq!(358 parse!("|||\n Hello world!\n a\n|||"),359 el!(Expr::Str("Hello world!\n a\n".into()), 0, 27),360 );361 assert_eq!(362 parse!("|||\n\t\tHello world!\n\t\t\ta\n|||"),363 el!(Expr::Str("Hello world!\n\ta\n".into()), 0, 27),364 );365 assert_eq!(366 parse!("|||\n Hello world!\n a\n |||"),367 el!(Expr::Str("Hello world!\n a\n".into()), 0, 30),368 );369 }370371 #[test]372 fn slice() {373 parse!("a[1:]");374 parse!("a[1::]");375 parse!("a[:1:]");376 parse!("a[::1]");377 parse!("str[:len - 1]");378 }379380 #[test]381 fn string_escaping() {382 assert_eq!(383 parse!(r#""Hello, \"world\"!""#),384 el!(Expr::Str(r#"Hello, "world"!"#.into()), 0, 19),385 );386 assert_eq!(387 parse!(r#"'Hello \'world\'!'"#),388 el!(Expr::Str("Hello 'world'!".into()), 0, 18),389 );390 assert_eq!(parse!(r#"'\\\\'"#), el!(Expr::Str("\\\\".into()), 0, 6));391 }392393 #[test]394 fn string_unescaping() {395 assert_eq!(396 parse!(r#""Hello\nWorld""#),397 el!(Expr::Str("Hello\nWorld".into()), 0, 14),398 );399 }400401 #[test]402 fn string_verbantim() {403 assert_eq!(404 parse!(r#"@"Hello\n""World""""#),405 el!(Expr::Str("Hello\\n\"World\"".into()), 0, 19),406 );407 }408409 #[test]410 fn imports() {411 assert_eq!(412 parse!("import \"hello\""),413 el!(Expr::Import(PathBuf::from("hello")), 0, 14),414 );415 assert_eq!(416 parse!("importstr \"garnish.txt\""),417 el!(Expr::ImportStr(PathBuf::from("garnish.txt")), 0, 23)418 );419 }420421 #[test]422 fn empty_object() {423 assert_eq!(424 parse!("{}"),425 el!(Expr::Obj(ObjBody::MemberList(vec![])), 0, 2)426 );427 }428429 #[test]430 fn basic_math() {431 assert_eq!(432 parse!("2+2*2"),433 el!(434 Expr::BinaryOp(435 el!(Expr::Num(2.0), 0, 1),436 Add,437 el!(438 Expr::BinaryOp(el!(Expr::Num(2.0), 2, 3), Mul, el!(Expr::Num(2.0), 4, 5)),439 2,440 5441 )442 ),443 0,444 5445 )446 );447 }448449 #[test]450 fn basic_math_with_indents() {451 assert_eq!(452 parse!("2 + 2 * 2 "),453 el!(454 Expr::BinaryOp(455 el!(Expr::Num(2.0), 0, 1),456 Add,457 el!(458 Expr::BinaryOp(el!(Expr::Num(2.0), 7, 8), Mul, el!(Expr::Num(2.0), 13, 14),),459 7,460 14461 ),462 ),463 0,464 14465 )466 );467 }468469 #[test]470 fn basic_math_parened() {471 assert_eq!(472 parse!("2+(2+2*2)"),473 el!(474 Expr::BinaryOp(475 el!(Expr::Num(2.0), 0, 1),476 Add,477 el!(478 Expr::Parened(el!(479 Expr::BinaryOp(480 el!(Expr::Num(2.0), 3, 4),481 Add,482 el!(483 Expr::BinaryOp(484 el!(Expr::Num(2.0), 5, 6),485 Mul,486 el!(Expr::Num(2.0), 7, 8),487 ),488 5,489 8490 ),491 ),492 3,493 8494 )),495 2,496 9497 ),498 ),499 0,500 9501 )502 );503 }504505 /// Comments should not affect parsing506 #[test]507 fn comments() {508 assert_eq!(509 parse!("2//comment\n+//comment\n3/*test*/*/*test*/4"),510 el!(511 Expr::BinaryOp(512 el!(Expr::Num(2.0), 0, 1),513 Add,514 el!(515 Expr::BinaryOp(516 el!(Expr::Num(3.0), 22, 23),517 Mul,518 el!(Expr::Num(4.0), 40, 41)519 ),520 22,521 41522 )523 ),524 0,525 41526 )527 );528 }529530 /// Comments should be able to be escaped531 #[test]532 fn comment_escaping() {533 assert_eq!(534 parse!("2/*\\*/+*/ - 22"),535 el!(536 Expr::BinaryOp(el!(Expr::Num(2.0), 0, 1), Sub, el!(Expr::Num(22.0), 12, 14)),537 0,538 14539 )540 );541 }542543 #[test]544 fn suffix() {545 // assert_eq!(parse!("std.test"), el!(Expr::Num(2.2)));546 // assert_eq!(parse!("std(2)"), el!(Expr::Num(2.2)));547 // assert_eq!(parse!("std.test(2)"), el!(Expr::Num(2.2)));548 // assert_eq!(parse!("a[b]"), el!(Expr::Num(2.2)))549 }550551 #[test]552 fn array_comp() {553 use Expr::*;554 /*555 `ArrComp(Apply(Index(Var("std") from "test.jsonnet":1-4, Var("deepJoin") from "test.jsonnet":5-13) from "test.jsonnet":1-13, ArgsDesc { unnamed: [Var("x") from "test.jsonnet":14-15], named: [] }, false) from "test.jsonnet":1-16, [ForSpec(ForSpecData("x", Var("arr") from "test.jsonnet":26-29))]) from "test.jsonnet":0-30`,556 `ArrComp(Apply(Index(Var("std") from "test.jsonnet":1-4, Str("deepJoin") from "test.jsonnet":5-13) from "test.jsonnet":1-13, ArgsDesc { unnamed: [Var("x") from "test.jsonnet":14-15], named: [] }, false) from "test.jsonnet":1-16, [ForSpec(ForSpecData("x", Var("arr") from "test.jsonnet":26-29))]) from "test.jsonnet":0-30`557 */558 assert_eq!(559 parse!("[std.deepJoin(x) for x in arr]"),560 el!(561 ArrComp(562 el!(563 Apply(564 el!(565 Index(566 el!(Var("std".into()), 1, 4),567 el!(Str("deepJoin".into()), 5, 13)568 ),569 1,570 13571 ),572 ArgsDesc::new(vec![el!(Var("x".into()), 14, 15)], vec![]),573 false,574 ),575 1,576 16577 ),578 vec![CompSpec::ForSpec(ForSpecData(579 "x".into(),580 el!(Var("arr".into()), 26, 29)581 ))]582 ),583 0,584 30585 ),586 )587 }588589 #[test]590 fn reserved() {591 use Expr::*;592 assert_eq!(parse!("null"), el!(Literal(LiteralType::Null), 0, 4));593 assert_eq!(parse!("nulla"), el!(Var("nulla".into()), 0, 5));594 }595596 #[test]597 fn multiple_args_buf() {598 parse!("a(b, null_fields)");599 }600601 #[test]602 fn infix_precedence() {603 use Expr::*;604 assert_eq!(605 parse!("!a && !b"),606 el!(607 BinaryOp(608 el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into()), 1, 2)), 0, 2),609 And,610 el!(UnaryOp(UnaryOpType::Not, el!(Var("b".into()), 7, 8)), 6, 8)611 ),612 0,613 8614 )615 );616 }617618 #[test]619 fn infix_precedence_division() {620 use Expr::*;621 assert_eq!(622 parse!("!a / !b"),623 el!(624 BinaryOp(625 el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into()), 1, 2)), 0, 2),626 Div,627 el!(UnaryOp(UnaryOpType::Not, el!(Var("b".into()), 6, 7)), 5, 7)628 ),629 0,630 7631 )632 );633 }634635 #[test]636 fn double_negation() {637 use Expr::*;638 assert_eq!(639 parse!("!!a"),640 el!(641 UnaryOp(642 UnaryOpType::Not,643 el!(UnaryOp(UnaryOpType::Not, el!(Var("a".into()), 2, 3)), 1, 3)644 ),645 0,646 3647 )648 )649 }650651 #[test]652 fn array_test_error() {653 parse!("[a for a in b if c for e in f]");654 // ^^^^ failed code655 }656657 #[test]658 fn missing_newline_between_comment_and_eof() {659 parse!(660 "{a:1}661662 //+213"663 );664 }665666 #[test]667 fn default_param_before_nondefault() {668 parse!("local x(foo = 'foo', bar) = null; null");669 }670671 #[test]672 fn can_parse_stdlib() {673 parse!(jrsonnet_stdlib::STDLIB_STR);674 }675676 #[test]677 fn add_location_info_to_all_sub_expressions() {678 use Expr::*;679680 let file_name: std::rc::Rc<std::path::Path> = PathBuf::from("test.jsonnet").into();681 let expr = parse(682 "{} { local x = 1, x: x } + {}",683 &ParserSettings {684 file_name: file_name.clone(),685 },686 )687 .unwrap();688 assert_eq!(689 expr,690 el!(691 BinaryOp(692 el!(693 ObjExtend(694 el!(Obj(ObjBody::MemberList(vec![])), 0, 2),695 ObjBody::MemberList(vec![696 Member::BindStmt(BindSpec {697 name: "x".into(),698 params: None,699 value: el!(Num(1.0), 15, 16)700 }),701 Member::Field(FieldMember {702 name: FieldName::Fixed("x".into()),703 plus: false,704 params: None,705 visibility: Visibility::Normal,706 value: el!(Var("x".into()), 21, 22),707 })708 ])709 ),710 0,711 24712 ),713 BinaryOpType::Add,714 el!(Obj(ObjBody::MemberList(vec![])), 27, 29),715 ),716 0,717 29718 ),719 );720 }721 // From source code722 /*723 #[bench]724 fn bench_parse_peg(b: &mut Bencher) {725 b.iter(|| parse!(jrsonnet_stdlib::STDLIB_STR))726 }727 */728}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),