git.delta.rocks / jrsonnet / refs/commits / 78be61658f34

difftreelog

refactor remove ManifestFormat from state

Yaroslav Bolyukin2022-11-09parent: #5f620e2.patch.diff
in: master

7 files changed

modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -93,7 +93,7 @@
 enum Error {
 	// Handled differently
 	#[error("evaluation error")]
-	Evaluation(jrsonnet_evaluator::error::LocError),
+	Evaluation(LocError),
 	#[error("io error")]
 	Io(#[from] std::io::Error),
 	#[error("input is not utf8 encoded")]
@@ -106,6 +106,11 @@
 		Self::Evaluation(e)
 	}
 }
+impl From<jrsonnet_evaluator::error::Error> for Error {
+	fn from(e: jrsonnet_evaluator::error::Error) -> Self {
+		Self::from(LocError::from(e))
+	}
+}
 
 fn main_catch(opts: Opts) -> bool {
 	let s = State::default();
@@ -144,9 +149,17 @@
 			dir.pop();
 			create_dir_all(dir)?;
 		}
-		for (file, data) in s.manifest_multi(val)?.iter() {
+		let Val::Obj(obj) = val else {
+			throw!("value should be object for --multi manifest, got {}", val.value_type())
+		};
+		for (field, data) in obj.iter(
+			#[cfg(feature = "exp-preserve-order")]
+			opts.manifest.preserve_order,
+		) {
+			let data = data.with_description(|| format!("getting field {field} for manifest"))?;
+
 			let mut path = multi.clone();
-			path.push(file as &str);
+			path.push(&field as &str);
 			if opts.output.create_output_dirs {
 				let mut dir = path.clone();
 				dir.pop();
@@ -154,7 +167,12 @@
 			}
 			println!("{}", path.to_str().expect("path"));
 			let mut file = File::create(path)?;
-			writeln!(file, "{}", data)?;
+			writeln!(
+				file,
+				"{}",
+				data.manifest(&manifest_format)
+					.with_description(|| format!("manifesting {field}"))?
+			)?;
 		}
 	} else if let Some(path) = opts.output.output_file {
 		if opts.output.create_output_dirs {
@@ -163,9 +181,9 @@
 			create_dir_all(dir)?;
 		}
 		let mut file = File::create(path)?;
-		writeln!(file, "{}", s.manifest(val)?)?;
+		writeln!(file, "{}", val.manifest(manifest_format)?)?;
 	} else {
-		let output = s.manifest(val)?;
+		let output = val.manifest(manifest_format)?;
 		if !output.is_empty() {
 			println!("{}", output);
 		}
modifiedcrates/jrsonnet-cli/src/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/manifest.rs
+++ b/crates/jrsonnet-cli/src/manifest.rs
@@ -1,7 +1,11 @@
 use std::{path::PathBuf, str::FromStr};
 
 use clap::{Parser, ValueEnum};
-use jrsonnet_evaluator::{error::Result, ManifestFormat, State};
+use jrsonnet_evaluator::{
+	error::Result,
+	stdlib::manifest::{JsonFormat, StringFormat, ToStringFormat, YamlFormat, YamlStreamFormat},
+	ManifestFormat, State,
+};
 
 use crate::ConfigureState;
 
@@ -36,7 +40,7 @@
 	#[clap(long, short = 'S', conflicts_with = "format")]
 	string: bool,
 	/// Write output as YAML stream, can be used with --format json/yaml
-	#[clap(long, short = 'y')]
+	#[clap(long, short = 'y', conflicts_with = "string")]
 	yaml_stream: bool,
 	/// Number of spaces to pad output manifest with.
 	/// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml]
@@ -45,34 +49,35 @@
 	/// Preserve order in object manifestification
 	#[cfg(feature = "exp-preserve-order")]
 	#[clap(long)]
-	exp_preserve_order: bool,
+	preserve_order: bool,
 }
 impl ConfigureState for ManifestOpts {
-	type Guards = ();
-	fn configure(&self, s: &State) -> Result<()> {
-		if self.string {
-			s.set_manifest_format(ManifestFormat::String);
+	type Guards = Box<dyn ManifestFormat>;
+	fn configure(&self, _s: &State) -> Result<Self::Guards> {
+		let format: Box<dyn ManifestFormat> = if self.string {
+			Box::new(StringFormat)
 		} else {
 			#[cfg(feature = "exp-preserve-order")]
-			let preserve_order = self.exp_preserve_order;
+			let preserve_order = self.preserve_order;
 			match self.format {
-				ManifestFormatName::String => s.set_manifest_format(ManifestFormat::String),
-				ManifestFormatName::Json => s.set_manifest_format(ManifestFormat::Json {
-					padding: self.line_padding.unwrap_or(3),
+				ManifestFormatName::String => Box::new(ToStringFormat),
+				ManifestFormatName::Json => Box::new(JsonFormat::cli(
+					self.line_padding.unwrap_or(3),
 					#[cfg(feature = "exp-preserve-order")]
 					preserve_order,
-				}),
-				ManifestFormatName::Yaml => s.set_manifest_format(ManifestFormat::Yaml {
-					padding: self.line_padding.unwrap_or(2),
+				)),
+				ManifestFormatName::Yaml => Box::new(YamlFormat::cli(
+					self.line_padding.unwrap_or(2),
 					#[cfg(feature = "exp-preserve-order")]
 					preserve_order,
-				}),
+				)),
 			}
-		}
-		if self.yaml_stream {
-			s.set_manifest_format(ManifestFormat::YamlStream(Box::new(s.manifest_format())))
-		}
-		Ok(())
+		};
+		Ok(if self.yaml_stream {
+			Box::new(YamlStreamFormat(format))
+		} else {
+			format
+		})
 	}
 }
 
modifiedcrates/jrsonnet-cli/src/stdlib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/stdlib.rs
+++ b/crates/jrsonnet-cli/src/stdlib.rs
@@ -60,8 +60,6 @@
 pub struct StdOpts {
 	/// Disable standard library.
 	/// By default standard library will be available via global `std` variable.
-	/// Note that standard library will still be loaded
-	/// if chosen manifestification method is not `none`.
 	#[clap(long)]
 	no_stdlib: bool,
 	/// Add string external variable.
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -149,10 +149,6 @@
 	pub context_initializer: TraceBox<dyn ContextInitializer>,
 	/// Used to resolve file locations/contents
 	pub import_resolver: TraceBox<dyn ImportResolver>,
-	/// Used in manifestification functions
-	pub manifest_format: ManifestFormat,
-	/// Used for bindings
-	pub trace_format: TraceBox<dyn TraceFormat>,
 }
 impl Default for EvaluationSettings {
 	fn default() -> Self {
@@ -447,26 +443,5 @@
 	}
 	pub fn context_initializer(&self) -> Ref<'_, dyn ContextInitializer> {
 		Ref::map(self.settings(), |s| &*s.context_initializer)
-	}
-
-	pub fn manifest_format(&self) -> ManifestFormat {
-		self.settings().manifest_format.clone()
-	}
-	pub fn set_manifest_format(&self, format: ManifestFormat) {
-		self.settings_mut().manifest_format = format;
-	}
-
-	pub fn trace_format(&self) -> Ref<'_, dyn TraceFormat> {
-		Ref::map(self.settings(), |s| &*s.trace_format)
-	}
-	pub fn set_trace_format(&self, format: impl TraceFormat) {
-		self.settings_mut().trace_format = tb!(format);
-	}
-
-	pub fn max_trace(&self) -> usize {
-		self.settings().max_trace
-	}
-	pub fn set_max_trace(&self, trace: usize) {
-		self.settings_mut().max_trace = trace;
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/stdlib/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stdlib/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/manifest.rs
@@ -1,6 +1,8 @@
+use std::{borrow::Cow, fmt::Write};
+
 use crate::{
 	error::{Error::*, Result},
-	throw, State, Val,
+	throw, ManifestFormat, State, Val,
 };
 
 #[derive(PartialEq, Eq, Clone, Copy)]
@@ -16,16 +18,88 @@
 	Minify,
 }
 
-pub struct ManifestJsonOptions<'s> {
-	pub padding: &'s str,
-	pub mtype: ManifestType,
-	pub newline: &'s str,
-	pub key_val_sep: &'s str,
+pub struct JsonFormat<'s> {
+	padding: Cow<'s, str>,
+	mtype: ManifestType,
+	newline: &'s str,
+	key_val_sep: &'s str,
 	#[cfg(feature = "exp-preserve-order")]
-	pub preserve_order: bool,
+	preserve_order: bool,
 }
 
-pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result<String> {
+impl<'s> JsonFormat<'s> {
+	// Minifying format
+	pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {
+		Self {
+			padding: Cow::Borrowed(""),
+			mtype: ManifestType::Minify,
+			newline: "\n",
+			key_val_sep: ":",
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order,
+		}
+	}
+	// Same format as std.toString
+	pub fn std_to_string() -> Self {
+		Self {
+			padding: Cow::Borrowed(""),
+			mtype: ManifestType::ToString,
+			newline: "\n",
+			key_val_sep: ": ",
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order: false,
+		}
+	}
+	pub fn std_to_json(
+		padding: String,
+		newline: &'s str,
+		key_val_sep: &'s str,
+		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+	) -> Self {
+		Self {
+			padding: Cow::Owned(padding),
+			mtype: ManifestType::Std,
+			newline,
+			key_val_sep,
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order,
+		}
+	}
+	// Same format as CLI manifestification
+	pub fn cli(
+		padding: usize,
+		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+	) -> Self {
+		if padding == 0 {
+			return Self::minify(
+				#[cfg(feature = "exp-preserve-order")]
+				preserve_order,
+			);
+		}
+		Self {
+			padding: Cow::Owned(" ".repeat(padding)),
+			mtype: ManifestType::Manifest,
+			newline: "\n",
+			key_val_sep: ": ",
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order,
+		}
+	}
+}
+impl Default for JsonFormat<'static> {
+	fn default() -> Self {
+		Self {
+			padding: Cow::Borrowed("    "),
+			mtype: ManifestType::Manifest,
+			newline: "\n",
+			key_val_sep: ": ",
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order: false,
+		}
+	}
+}
+
+pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {
 	let mut out = String::new();
 	manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;
 	Ok(out)
@@ -34,9 +108,8 @@
 	val: &Val,
 	buf: &mut String,
 	cur_padding: &mut String,
-	options: &ManifestJsonOptions<'_>,
+	options: &JsonFormat<'_>,
 ) -> Result<()> {
-	use std::fmt::Write;
 	let mtype = options.mtype;
 	match val {
 		Val::Bool(v) => {
@@ -57,7 +130,7 @@
 				}
 
 				let old_len = cur_padding.len();
-				cur_padding.push_str(options.padding);
+				cur_padding.push_str(&options.padding);
 				for (i, item) in items.iter().enumerate() {
 					if i != 0 {
 						buf.push(',');
@@ -97,7 +170,7 @@
 				}
 
 				let old_len = cur_padding.len();
-				cur_padding.push_str(options.padding);
+				cur_padding.push_str(&options.padding);
 				for (i, field) in fields.into_iter().enumerate() {
 					if i != 0 {
 						buf.push(',');
@@ -138,6 +211,48 @@
 	Ok(())
 }
 
+impl ManifestFormat for JsonFormat<'_> {
+	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+		manifest_json_ex_buf(&val, buf, &mut String::new(), &self)
+	}
+}
+
+pub struct ToStringFormat;
+impl ManifestFormat for ToStringFormat {
+	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {
+		JsonFormat::std_to_string().manifest_buf(val, out)
+	}
+}
+pub struct StringFormat;
+impl ManifestFormat for StringFormat {
+	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {
+		let Val::Str(s) = val else {
+			throw!("output should be string for string manifest format, got {}", val.value_type())
+		};
+		out.write_str(&s).unwrap();
+		Ok(())
+	}
+}
+
+pub struct YamlStreamFormat<I>(pub I);
+impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {
+	fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {
+		let Val::Arr(arr) = val else {
+			throw!("output should be array for yaml stream format, got {}", val.value_type())
+		};
+		if !arr.is_empty() {
+			for v in arr.iter() {
+				let v = v?;
+				out.push_str("---\n");
+				self.0.manifest_buf(v, out)?;
+				out.push('\n');
+			}
+			out.push_str("...");
+		}
+		Ok(())
+	}
+}
+
 pub fn escape_string_json(s: &str) -> String {
 	let mut buf = String::new();
 	escape_string_json_buf(s, &mut buf);
@@ -145,7 +260,6 @@
 }
 
 fn escape_string_json_buf(s: &str, buf: &mut String) {
-	use std::fmt::Write;
 	buf.push('"');
 	for c in s.chars() {
 		match c {
@@ -165,33 +279,66 @@
 	buf.push('"');
 }
 
-pub struct ManifestYamlOptions<'s> {
+pub struct YamlFormat<'s> {
 	/// Padding before fields, i.e
 	/// ```yaml
 	/// a:
 	///   b:
 	/// ## <- this
 	/// ```
-	pub padding: &'s str,
+	padding: Cow<'s, str>,
 	/// Padding before array elements in objects
 	/// ```yaml
 	/// a:
 	///   - 1
 	/// ## <- this
 	/// ```
-	pub arr_element_padding: &'s str,
+	arr_element_padding: Cow<'s, str>,
 	/// Should yaml keys appear unescaped, when possible
 	/// ```yaml
 	/// "safe_key": 1
 	/// # vs
 	/// safe_key: 1
 	/// ```
-	pub quote_keys: bool,
+	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,
+	preserve_order: bool,
 }
+impl YamlFormat<'_> {
+	pub fn cli(
+		padding: usize,
+		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+	) -> Self {
+		let padding = " ".repeat(padding);
+		Self {
+			padding: Cow::Owned(padding.clone()),
+			arr_element_padding: Cow::Owned(padding),
+			quote_keys: false,
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order,
+		}
+	}
+	pub fn std_to_yaml(
+		indent_array_in_object: bool,
+		quote_keys: bool,
+		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+	) -> Self {
+		Self {
+			padding: Cow::Borrowed("  "),
+			arr_element_padding: Cow::Borrowed(if indent_array_in_object { "  " } else { "" }),
+			quote_keys,
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order,
+		}
+	}
+}
+impl ManifestFormat for YamlFormat<'_> {
+	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+		manifest_yaml_ex_buf(&val, buf, &mut String::new(), self)
+	}
+}
 
 /// From <https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289>
 /// With added date check
@@ -221,7 +368,7 @@
 		|| string.parse::<f64>().is_ok()
 }
 
-pub fn manifest_yaml_ex(val: &Val, options: &ManifestYamlOptions<'_>) -> Result<String> {
+pub fn manifest_yaml_ex(val: &Val, options: &YamlFormat<'_>) -> Result<String> {
 	let mut out = String::new();
 	manifest_yaml_ex_buf(val, &mut out, &mut String::new(), options)?;
 	Ok(out)
@@ -232,9 +379,8 @@
 	val: &Val,
 	buf: &mut String,
 	cur_padding: &mut String,
-	options: &ManifestYamlOptions<'_>,
+	options: &YamlFormat<'_>,
 ) -> Result<()> {
-	use std::fmt::Write;
 	match val {
 		Val::Bool(v) => {
 			if *v {
@@ -252,7 +398,7 @@
 				for line in s.split('\n') {
 					buf.push('\n');
 					buf.push_str(cur_padding);
-					buf.push_str(options.padding);
+					buf.push_str(&options.padding);
 					buf.push_str(line);
 				}
 			} else if !options.quote_keys && !yaml_needs_quotes(s) {
@@ -277,7 +423,7 @@
 						Val::Arr(a) if !a.is_empty() => {
 							buf.push('\n');
 							buf.push_str(cur_padding);
-							buf.push_str(options.padding);
+							buf.push_str(&options.padding);
 						}
 						_ => buf.push(' '),
 					}
@@ -288,7 +434,7 @@
 					};
 					let prev_len = cur_padding.len();
 					if extra_padding {
-						cur_padding.push_str(options.padding);
+						cur_padding.push_str(&options.padding);
 					}
 					manifest_yaml_ex_buf(&item, buf, cur_padding, options)?;
 					cur_padding.truncate(prev_len);
@@ -323,14 +469,14 @@
 						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);
+							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_str(&options.padding);
+							cur_padding.push_str(&options.padding);
 						}
 						_ => buf.push(' '),
 					}
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
1use std::{cell::RefCell, fmt::Debug, rc::Rc};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::{IBytes, IStr};5use jrsonnet_types::ValType;67use crate::{8	error::{Error::*, LocError},9	function::FuncVal,10	gc::{GcHashMap, TraceBox},11	stdlib::manifest::{12		manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, ManifestYamlOptions,13	},14	throw,15	typed::BoundedUsize,16	ObjValue, Result, Unbound, WeakObjValue,17};1819pub trait ThunkValue: Trace {20	type Output;21	fn get(self: Box<Self>) -> Result<Self::Output>;22}2324#[derive(Trace)]25enum ThunkInner<T: Trace> {26	Computed(T),27	Errored(LocError),28	Waiting(TraceBox<dyn ThunkValue<Output = T>>),29	Pending,30}3132#[allow(clippy::module_name_repetitions)]33#[derive(Clone, Trace)]34pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);3536impl<T> Thunk<T>37where38	T: Clone + Trace,39{40	pub fn new(f: TraceBox<dyn ThunkValue<Output = T>>) -> Self {41		Self(Cc::new(RefCell::new(ThunkInner::Waiting(f))))42	}43	pub fn evaluated(val: T) -> Self {44		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))45	}46	pub fn force(&self) -> Result<()> {47		self.evaluate()?;48		Ok(())49	}50	pub fn evaluate(&self) -> Result<T> {51		match &*self.0.borrow() {52			ThunkInner::Computed(v) => return Ok(v.clone()),53			ThunkInner::Errored(e) => return Err(e.clone()),54			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),55			ThunkInner::Waiting(..) => (),56		};57		let ThunkInner::Waiting(value) = std::mem::replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) else {58			unreachable!();59		};60		let new_value = match value.0.get() {61			Ok(v) => v,62			Err(e) => {63				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());64				return Err(e);65			}66		};67		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());68		Ok(new_value)69	}70}7172type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);7374#[derive(Trace, Clone)]75pub struct CachedUnbound<I, T>76where77	I: Unbound<Bound = T>,78	T: Trace,79{80	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,81	value: I,82}83impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {84	pub fn new(value: I) -> Self {85		Self {86			cache: Cc::new(RefCell::new(GcHashMap::new())),87			value,88		}89	}90}91impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {92	type Bound = T;93	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {94		let cache_key = (95			sup.as_ref().map(|s| s.clone().downgrade()),96			this.as_ref().map(|t| t.clone().downgrade()),97		);98		{99			if let Some(t) = self.cache.borrow().get(&cache_key) {100				return Ok(t.clone());101			}102		}103		let bound = self.value.bind(sup, this)?;104105		{106			let mut cache = self.cache.borrow_mut();107			cache.insert(cache_key, bound.clone());108		}109110		Ok(bound)111	}112}113114impl<T: Debug + Trace> Debug for Thunk<T> {115	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {116		write!(f, "Lazy")117	}118}119impl<T: Trace> PartialEq for Thunk<T> {120	fn eq(&self, other: &Self) -> bool {121		Cc::ptr_eq(&self.0, &other.0)122	}123}124125#[derive(Clone, Trace)]126pub enum ManifestFormat {127	YamlStream(Box<ManifestFormat>),128	Yaml {129		padding: usize,130		#[cfg(feature = "exp-preserve-order")]131		preserve_order: bool,132	},133	Json {134		padding: usize,135		#[cfg(feature = "exp-preserve-order")]136		preserve_order: bool,137	},138	ToString,139	String,140}141impl ManifestFormat {142	#[cfg(feature = "exp-preserve-order")]143	fn preserve_order(&self) -> bool {144		match self {145			ManifestFormat::YamlStream(s) => s.preserve_order(),146			ManifestFormat::Yaml { preserve_order, .. } => *preserve_order,147			ManifestFormat::Json { preserve_order, .. } => *preserve_order,148			ManifestFormat::ToString => false,149			ManifestFormat::String => false,150		}151	}152}153154#[derive(Debug, Clone, Trace)]155pub struct Slice {156	pub(crate) inner: ArrValue,157	pub(crate) from: u32,158	pub(crate) to: u32,159	pub(crate) step: u32,160}161impl Slice {162	const fn from(&self) -> usize {163		self.from as usize164	}165	const fn to(&self) -> usize {166		self.to as usize167	}168	const fn step(&self) -> usize {169		self.step as usize170	}171	const fn len(&self) -> usize {172		// TODO: use div_ceil173		let diff = self.to() - self.from();174		let rem = diff % self.step();175		let div = diff / self.step();176177		if rem == 0 {178			div179		} else {180			div + 1181		}182	}183}184185/// Represents a Jsonnet array value.186#[derive(Debug, Clone, Trace)]187// may contrain other ArrValue188#[trace(tracking(force))]189pub enum ArrValue {190	/// Layout optimized byte array.191	Bytes(#[trace(skip)] IBytes),192	/// Every element is lazy evaluated.193	Lazy(Cc<Vec<Thunk<Val>>>),194	/// Every field is already evaluated.195	Eager(Cc<Vec<Val>>),196	/// Concatenation of two arrays of any kind.197	Extended(Box<(Self, Self)>),198	/// Represents a integer array in form `[start, start + 1, ... end - 1, end]`.199	/// This kind of arrays is generated by `std.range(start, end)` call, and used for loops.200	Range(i32, i32),201	/// Sliced array view.202	Slice(Box<Slice>),203	/// Reversed array view.204	/// Returned by `std.reverse(other)` call205	Reversed(Box<Self>),206}207208#[cfg(target_pointer_width = "64")]209static_assertions::assert_eq_size!(ArrValue, [u8; 16]);210211impl ArrValue {212	pub fn new_eager() -> Self {213		Self::Eager(Cc::new(Vec::new()))214	}215	pub fn empty() -> Self {216		Self::new_range(0, 0)217	}218219	/// # Panics220	/// If a > b221	#[inline]222	pub fn new_range(a: i32, b: i32) -> Self {223		assert!(a <= b);224		Self::Range(a, b)225	}226227	/// # Panics228	/// If passed numbers are incorrect229	#[must_use]230	pub fn slice(self, from: Option<usize>, to: Option<usize>, step: Option<usize>) -> Self {231		let len = self.len();232		let from = from.unwrap_or(0);233		let to = to.unwrap_or(len).min(len);234		let step = step.unwrap_or(1);235		assert!(from < to);236		assert!(step > 0);237238		Self::Slice(Box::new(Slice {239			inner: self,240			from: from as u32,241			to: to as u32,242			step: step as u32,243		}))244	}245246	/// Array length.247	pub fn len(&self) -> usize {248		match self {249			Self::Bytes(i) => i.len(),250			Self::Lazy(l) => l.len(),251			Self::Eager(e) => e.len(),252			Self::Extended(v) => v.0.len() + v.1.len(),253			Self::Range(a, b) => a.abs_diff(*b) as usize + 1,254			Self::Reversed(i) => i.len(),255			Self::Slice(s) => s.len(),256		}257	}258259	/// Is array contains no elements?260	pub fn is_empty(&self) -> bool {261		self.len() == 0262	}263264	/// Get array element by index, evaluating it, if it is lazy.265	///266	/// Returns `None` on out-of-bounds condition.267	pub fn get(&self, index: usize) -> Result<Option<Val>> {268		match self {269			Self::Bytes(i) => i270				.get(index)271				.map_or(Ok(None), |v| Ok(Some(Val::Num(f64::from(*v))))),272			Self::Lazy(vec) => {273				if let Some(v) = vec.get(index) {274					Ok(Some(v.evaluate()?))275				} else {276					Ok(None)277				}278			}279			Self::Eager(vec) => Ok(vec.get(index).cloned()),280			Self::Extended(v) => {281				let a_len = v.0.len();282				if a_len > index {283					v.0.get(index)284				} else {285					v.1.get(index - a_len)286				}287			}288			Self::Range(a, _) => {289				if index >= self.len() {290					return Ok(None);291				}292				Ok(Some(Val::Num(((*a as isize) + index as isize) as f64)))293			}294			Self::Reversed(v) => {295				let len = v.len();296				if index >= len {297					return Ok(None);298				}299				v.get(len - index - 1)300			}301			Self::Slice(v) => {302				let index = v.from() + index * v.step();303				if index >= v.to() {304					return Ok(None);305				}306				v.inner.get(index)307			}308		}309	}310311	/// Get array element by index, without evaluation.312	///313	/// Returns `None` on out-of-bounds condition.314	pub fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {315		match self {316			Self::Bytes(i) => i317				.get(index)318				.map(|b| Thunk::evaluated(Val::Num(f64::from(*b)))),319			Self::Lazy(vec) => vec.get(index).cloned(),320			Self::Eager(vec) => vec.get(index).cloned().map(Thunk::evaluated),321			Self::Extended(v) => {322				let a_len = v.0.len();323				if a_len > index {324					v.0.get_lazy(index)325				} else {326					v.1.get_lazy(index - a_len)327				}328			}329			Self::Range(a, _) => {330				if index >= self.len() {331					return None;332				}333				Some(Thunk::evaluated(Val::Num(334					((*a as isize) + index as isize) as f64,335				)))336			}337			Self::Reversed(v) => {338				let len = v.len();339				if index >= len {340					return None;341				}342				v.get_lazy(len - index - 1)343			}344			Self::Slice(s) => {345				let index = s.from() + index * s.step();346				if index >= s.to() {347					return None;348				}349				s.inner.get_lazy(index)350			}351		}352	}353354	/// Evaluate all array elements, returning new array.355	pub fn evaluated(&self) -> Result<Cc<Vec<Val>>> {356		Ok(match self {357			Self::Bytes(i) => {358				let mut out = Vec::with_capacity(i.len());359				for v in i.iter() {360					out.push(Val::Num(f64::from(*v)));361				}362				Cc::new(out)363			}364			Self::Lazy(vec) => {365				let mut out = Vec::with_capacity(vec.len());366				for item in vec.iter() {367					out.push(item.evaluate()?);368				}369				Cc::new(out)370			}371			Self::Eager(vec) => vec.clone(),372			Self::Extended(_v) => {373				let mut out = Vec::with_capacity(self.len());374				for item in self.iter() {375					out.push(item?);376				}377				Cc::new(out)378			}379			Self::Range(a, b) => {380				let mut out = Vec::with_capacity(self.len());381				for i in *a..*b {382					out.push(Val::Num(f64::from(i)));383				}384				Cc::new(out)385			}386			Self::Reversed(r) => {387				let mut r = r.evaluated()?;388				Cc::update_with(&mut r, |v| v.reverse());389				r390			}391			Self::Slice(v) => {392				let mut out = Vec::with_capacity(v.inner.len());393				for v in v394					.inner395					.iter_lazy()396					.skip(v.from())397					.take(v.to() - v.from())398					.step_by(v.step())399				{400					out.push(v.evaluate()?);401				}402				Cc::new(out)403			}404		})405	}406407	/// Iterate over elements, evaluating them.408	pub fn iter(&self) -> impl DoubleEndedIterator<Item = Result<Val>> + '_ {409		(0..self.len()).map(move |idx| match self {410			Self::Bytes(b) => Ok(Val::Num(f64::from(b[idx]))),411			Self::Lazy(l) => l[idx].evaluate(),412			Self::Eager(e) => Ok(e[idx].clone()),413			Self::Extended(..) | Self::Range(..) | Self::Reversed(..) | Self::Slice(..) => {414				self.get(idx).map(|e| e.expect("idx < len"))415			}416		})417	}418419	/// Iterate over elements, returning lazy values.420	pub fn iter_lazy(&self) -> impl DoubleEndedIterator<Item = Thunk<Val>> + '_ {421		(0..self.len()).map(move |idx| match self {422			Self::Bytes(b) => Thunk::evaluated(Val::Num(f64::from(b[idx]))),423			Self::Lazy(l) => l[idx].clone(),424			Self::Eager(e) => Thunk::evaluated(e[idx].clone()),425			Self::Slice(..) | Self::Extended(..) | Self::Range(..) | Self::Reversed(..) => {426				self.get_lazy(idx).expect("idx < len")427			}428		})429	}430431	/// Return a reversed view on current array.432	#[must_use]433	pub fn reversed(self) -> Self {434		Self::Reversed(Box::new(self))435	}436437	/// Return a new array, produced by passing every element of current array to specified callback function.438	pub fn map(self, mapper: impl Fn(Val) -> Result<Val>) -> Result<Self> {439		let mut out = Vec::with_capacity(self.len());440441		for value in self.iter() {442			out.push(mapper(value?)?);443		}444445		Ok(Self::Eager(Cc::new(out)))446	}447448	/// Return a new array, produced from current array by removing every value, for which specified callback function returns false.449	pub fn filter(self, filter: impl Fn(&Val) -> Result<bool>) -> Result<Self> {450		let mut out = Vec::with_capacity(self.len());451452		for value in self.iter() {453			let value = value?;454			if filter(&value)? {455				out.push(value);456			}457		}458459		Ok(Self::Eager(Cc::new(out)))460	}461462	pub fn ptr_eq(a: &Self, b: &Self) -> bool {463		match (a, b) {464			(Self::Lazy(a), Self::Lazy(b)) => Cc::ptr_eq(a, b),465			(Self::Eager(a), Self::Eager(b)) => Cc::ptr_eq(a, b),466			_ => false,467		}468	}469}470471impl From<Vec<Thunk<Val>>> for ArrValue {472	fn from(v: Vec<Thunk<Val>>) -> Self {473		Self::Lazy(Cc::new(v))474	}475}476477impl From<Vec<Val>> for ArrValue {478	fn from(v: Vec<Val>) -> Self {479		Self::Eager(Cc::new(v))480	}481}482483/// Represents a Jsonnet value, which can be spliced or indexed (string or array).484#[allow(clippy::module_name_repetitions)]485pub enum IndexableVal {486	/// String.487	Str(IStr),488	/// Array.489	Arr(ArrValue),490}491impl IndexableVal {492	/// Slice the value.493	///494	/// # Implementation495	///496	/// For strings, will create a copy of specified interval.497	///498	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.499	pub fn slice(500		self,501		index: Option<BoundedUsize<0, { i32::MAX as usize }>>,502		end: Option<BoundedUsize<0, { i32::MAX as usize }>>,503		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,504	) -> Result<Self> {505		match &self {506			IndexableVal::Str(s) => {507				let index = index.as_deref().copied().unwrap_or(0);508				let end = end.as_deref().copied().unwrap_or(usize::MAX);509				let step = step.as_deref().copied().unwrap_or(1);510511				if index >= end {512					return Ok(Self::Str("".into()));513				}514515				Ok(Self::Str(516					(s.chars()517						.skip(index)518						.take(end - index)519						.step_by(step)520						.collect::<String>())521					.into(),522				))523			}524			IndexableVal::Arr(arr) => {525				let index = index.as_deref().copied().unwrap_or(0);526				let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());527				let step = step.as_deref().copied().unwrap_or(1);528529				if index >= end {530					return Ok(Self::Arr(ArrValue::new_eager()));531				}532533				Ok(Self::Arr(ArrValue::Slice(Box::new(Slice {534					inner: arr.clone(),535					from: index as u32,536					to: end as u32,537					step: step as u32,538				}))))539			}540		}541	}542}543544/// Represents any valid Jsonnet value.545#[derive(Debug, Clone, Trace)]546pub enum Val {547	/// Represents a Jsonnet boolean.548	Bool(bool),549	/// Represents a Jsonnet null value.550	Null,551	/// Represents a Jsonnet string.552	Str(IStr),553	/// Represents a Jsonnet number.554	/// Should be finite, and not NaN555	/// This restriction isn't enforced by enum, as enum field can't be marked as private556	Num(f64),557	/// Represents a Jsonnet array.558	Arr(ArrValue),559	/// Represents a Jsonnet object.560	Obj(ObjValue),561	/// Represents a Jsonnet function.562	Func(FuncVal),563}564565impl From<IndexableVal> for Val {566	fn from(v: IndexableVal) -> Self {567		match v {568			IndexableVal::Str(s) => Self::Str(s),569			IndexableVal::Arr(a) => Self::Arr(a),570		}571	}572}573574// Broken between stable and nightly, as there is new layout size optimization575// #[cfg(target_pointer_width = "64")]576// static_assertions::assert_eq_size!(Val, [u8; 24]);577578impl Val {579	pub const fn as_bool(&self) -> Option<bool> {580		match self {581			Self::Bool(v) => Some(*v),582			_ => None,583		}584	}585	pub const fn as_null(&self) -> Option<()> {586		match self {587			Self::Null => Some(()),588			_ => None,589		}590	}591	pub fn as_str(&self) -> Option<IStr> {592		match self {593			Self::Str(s) => Some(s.clone()),594			_ => None,595		}596	}597	pub const fn as_num(&self) -> Option<f64> {598		match self {599			Self::Num(n) => Some(*n),600			_ => None,601		}602	}603	pub fn as_arr(&self) -> Option<ArrValue> {604		match self {605			Self::Arr(a) => Some(a.clone()),606			_ => None,607		}608	}609	pub fn as_obj(&self) -> Option<ObjValue> {610		match self {611			Self::Obj(o) => Some(o.clone()),612			_ => None,613		}614	}615	pub fn as_func(&self) -> Option<FuncVal> {616		match self {617			Self::Func(f) => Some(f.clone()),618			_ => None,619		}620	}621622	/// Creates `Val::Num` after checking for numeric overflow.623	/// As numbers are `f64`, we can just check for their finity.624	pub fn new_checked_num(num: f64) -> Result<Self> {625		if num.is_finite() {626			Ok(Self::Num(num))627		} else {628			throw!("overflow")629		}630	}631632	pub const fn value_type(&self) -> ValType {633		match self {634			Self::Str(..) => ValType::Str,635			Self::Num(..) => ValType::Num,636			Self::Arr(..) => ValType::Arr,637			Self::Obj(..) => ValType::Obj,638			Self::Bool(_) => ValType::Bool,639			Self::Null => ValType::Null,640			Self::Func(..) => ValType::Func,641		}642	}643644	pub fn to_string(&self) -> Result<IStr> {645		Ok(match self {646			Self::Bool(true) => "true".into(),647			Self::Bool(false) => "false".into(),648			Self::Null => "null".into(),649			Self::Str(s) => s.clone(),650			v => manifest_json_ex(651				v,652				&ManifestJsonOptions {653					padding: "",654					mtype: ManifestType::ToString,655					newline: "\n",656					key_val_sep: ": ",657					#[cfg(feature = "exp-preserve-order")]658					preserve_order: false,659				},660			)?661			.into(),662		})663	}664665	/// Expects value to be object, outputs (key, manifested value) pairs666	pub fn manifest_multi(&self, ty: &ManifestFormat) -> Result<Vec<(IStr, IStr)>> {667		let Self::Obj(obj) = self else {668			throw!(MultiManifestOutputIsNotAObject);669		};670		let keys = obj.fields(671			#[cfg(feature = "exp-preserve-order")]672			ty.preserve_order(),673		);674		let mut out = Vec::with_capacity(keys.len());675		for key in keys {676			let value = obj677				.get(key.clone())?678				.expect("item in object")679				.manifest(ty)?;680			out.push((key, value));681		}682		Ok(out)683	}684685	/// Expects value to be array, outputs manifested values686	pub fn manifest_stream(&self, ty: &ManifestFormat) -> Result<Vec<IStr>> {687		let Self::Arr(arr) = self else {688			throw!(StreamManifestOutputIsNotAArray);689		};690		let mut out = Vec::with_capacity(arr.len());691		for i in arr.iter() {692			out.push(i?.manifest(ty)?);693		}694		Ok(out)695	}696697	pub fn manifest(&self, ty: &ManifestFormat) -> Result<IStr> {698		Ok(match ty {699			ManifestFormat::YamlStream(format) => {700				let Self::Arr(arr) = self else {701					throw!(StreamManifestOutputIsNotAArray)702				};703				let mut out = String::new();704705				match format as &ManifestFormat {706					ManifestFormat::YamlStream(_) => throw!(StreamManifestOutputCannotBeRecursed),707					ManifestFormat::String => throw!(StreamManifestCannotNestString),708					_ => {}709				};710711				if !arr.is_empty() {712					for v in arr.iter() {713						out.push_str("---\n");714						out.push_str(&v?.manifest(format)?);715						out.push('\n');716					}717					out.push_str("...");718				}719720				out.into()721			}722			ManifestFormat::Yaml {723				padding,724				#[cfg(feature = "exp-preserve-order")]725				preserve_order,726			} => self.to_yaml(727				*padding,728				#[cfg(feature = "exp-preserve-order")]729				*preserve_order,730			)?,731			ManifestFormat::Json {732				padding,733				#[cfg(feature = "exp-preserve-order")]734				preserve_order,735			} => self.to_json(736				*padding,737				#[cfg(feature = "exp-preserve-order")]738				*preserve_order,739			)?,740			ManifestFormat::ToString => self.to_string()?,741			ManifestFormat::String => match self {742				Self::Str(s) => s.clone(),743				_ => throw!(StringManifestOutputIsNotAString),744			},745		})746	}747748	/// For manifestification749	pub fn to_json(750		&self,751		padding: usize,752		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,753	) -> Result<IStr> {754		manifest_json_ex(755			self,756			&ManifestJsonOptions {757				padding: &" ".repeat(padding),758				mtype: if padding == 0 {759					ManifestType::Minify760				} else {761					ManifestType::Manifest762				},763				newline: "\n",764				key_val_sep: ": ",765				#[cfg(feature = "exp-preserve-order")]766				preserve_order,767			},768		)769		.map(Into::into)770	}771772	/// Calls `std.manifestJson`773	pub fn to_std_json(774		&self,775		padding: usize,776		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,777	) -> Result<Rc<str>> {778		manifest_json_ex(779			self,780			&ManifestJsonOptions {781				padding: &" ".repeat(padding),782				mtype: ManifestType::Std,783				newline: "\n",784				key_val_sep: ": ",785				#[cfg(feature = "exp-preserve-order")]786				preserve_order,787			},788		)789		.map(Into::into)790	}791792	pub fn to_yaml(793		&self,794		padding: usize,795		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,796	) -> Result<IStr> {797		let padding = &" ".repeat(padding);798		manifest_yaml_ex(799			self,800			&ManifestYamlOptions {801				padding,802				arr_element_padding: padding,803				quote_keys: false,804				#[cfg(feature = "exp-preserve-order")]805				preserve_order,806			},807		)808		.map(Into::into)809	}810	pub fn into_indexable(self) -> Result<IndexableVal> {811		Ok(match self {812			Val::Str(s) => IndexableVal::Str(s),813			Val::Arr(arr) => IndexableVal::Arr(arr),814			_ => throw!(ValueIsNotIndexable(self.value_type())),815		})816	}817}818819const fn is_function_like(val: &Val) -> bool {820	matches!(val, Val::Func(_))821}822823/// Native implementation of `std.primitiveEquals`824pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {825	Ok(match (val_a, val_b) {826		(Val::Bool(a), Val::Bool(b)) => a == b,827		(Val::Null, Val::Null) => true,828		(Val::Str(a), Val::Str(b)) => a == b,829		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,830		(Val::Arr(_), Val::Arr(_)) => {831			throw!("primitiveEquals operates on primitive types, got array")832		}833		(Val::Obj(_), Val::Obj(_)) => {834			throw!("primitiveEquals operates on primitive types, got object")835		}836		(a, b) if is_function_like(a) && is_function_like(b) => {837			throw!("cannot test equality of functions")838		}839		(_, _) => false,840	})841}842843/// Native implementation of `std.equals`844pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {845	if val_a.value_type() != val_b.value_type() {846		return Ok(false);847	}848	match (val_a, val_b) {849		(Val::Arr(a), Val::Arr(b)) => {850			if ArrValue::ptr_eq(a, b) {851				return Ok(true);852			}853			if a.len() != b.len() {854				return Ok(false);855			}856			for (a, b) in a.iter().zip(b.iter()) {857				if !equals(&a?, &b?)? {858					return Ok(false);859				}860			}861			Ok(true)862		}863		(Val::Obj(a), Val::Obj(b)) => {864			if ObjValue::ptr_eq(a, b) {865				return Ok(true);866			}867			let fields = a.fields(868				#[cfg(feature = "exp-preserve-order")]869				false,870			);871			if fields872				!= b.fields(873					#[cfg(feature = "exp-preserve-order")]874					false,875				) {876				return Ok(false);877			}878			for field in fields {879				if !equals(880					&a.get(field.clone())?.expect("field exists"),881					&b.get(field)?.expect("field exists"),882				)? {883					return Ok(false);884				}885			}886			Ok(true)887		}888		(a, b) => Ok(primitive_equals(a, b)?),889	}890}
after · crates/jrsonnet-evaluator/src/val.rs
1use std::{cell::RefCell, fmt::Debug};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::{IBytes, IStr};5use jrsonnet_types::ValType;67use crate::{8	error::{Error::*, LocError},9	function::FuncVal,10	gc::{GcHashMap, TraceBox},11	throw,12	typed::BoundedUsize,13	ObjValue, Result, Unbound, WeakObjValue,14};1516pub trait ThunkValue: Trace {17	type Output;18	fn get(self: Box<Self>) -> Result<Self::Output>;19}2021#[derive(Trace)]22enum ThunkInner<T: Trace> {23	Computed(T),24	Errored(LocError),25	Waiting(TraceBox<dyn ThunkValue<Output = T>>),26	Pending,27}2829#[allow(clippy::module_name_repetitions)]30#[derive(Clone, Trace)]31pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);3233impl<T> Thunk<T>34where35	T: Clone + Trace,36{37	pub fn new(f: TraceBox<dyn ThunkValue<Output = T>>) -> Self {38		Self(Cc::new(RefCell::new(ThunkInner::Waiting(f))))39	}40	pub fn evaluated(val: T) -> Self {41		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))42	}43	pub fn force(&self) -> Result<()> {44		self.evaluate()?;45		Ok(())46	}47	pub fn evaluate(&self) -> Result<T> {48		match &*self.0.borrow() {49			ThunkInner::Computed(v) => return Ok(v.clone()),50			ThunkInner::Errored(e) => return Err(e.clone()),51			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),52			ThunkInner::Waiting(..) => (),53		};54		let ThunkInner::Waiting(value) = std::mem::replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) else {55			unreachable!();56		};57		let new_value = match value.0.get() {58			Ok(v) => v,59			Err(e) => {60				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());61				return Err(e);62			}63		};64		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());65		Ok(new_value)66	}67}6869type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);7071#[derive(Trace, Clone)]72pub struct CachedUnbound<I, T>73where74	I: Unbound<Bound = T>,75	T: Trace,76{77	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,78	value: I,79}80impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {81	pub fn new(value: I) -> Self {82		Self {83			cache: Cc::new(RefCell::new(GcHashMap::new())),84			value,85		}86	}87}88impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {89	type Bound = T;90	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {91		let cache_key = (92			sup.as_ref().map(|s| s.clone().downgrade()),93			this.as_ref().map(|t| t.clone().downgrade()),94		);95		{96			if let Some(t) = self.cache.borrow().get(&cache_key) {97				return Ok(t.clone());98			}99		}100		let bound = self.value.bind(sup, this)?;101102		{103			let mut cache = self.cache.borrow_mut();104			cache.insert(cache_key, bound.clone());105		}106107		Ok(bound)108	}109}110111impl<T: Debug + Trace> Debug for Thunk<T> {112	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {113		write!(f, "Lazy")114	}115}116impl<T: Trace> PartialEq for Thunk<T> {117	fn eq(&self, other: &Self) -> bool {118		Cc::ptr_eq(&self.0, &other.0)119	}120}121122pub trait ManifestFormat {123	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;124	fn manifest(&self, val: Val) -> Result<String> {125		let mut out = String::new();126		self.manifest_buf(val, &mut out)?;127		Ok(out)128	}129}130impl<T> ManifestFormat for Box<T>131where132	T: ManifestFormat + ?Sized,133{134	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {135		let inner = &**self;136		inner.manifest_buf(val, buf)137	}138}139impl<T> ManifestFormat for &'_ T140where141	T: ManifestFormat + ?Sized,142{143	fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {144		let inner = &**self;145		inner.manifest_buf(val, buf)146	}147}148149#[derive(Debug, Clone, Trace)]150pub struct Slice {151	pub(crate) inner: ArrValue,152	pub(crate) from: u32,153	pub(crate) to: u32,154	pub(crate) step: u32,155}156impl Slice {157	const fn from(&self) -> usize {158		self.from as usize159	}160	const fn to(&self) -> usize {161		self.to as usize162	}163	const fn step(&self) -> usize {164		self.step as usize165	}166	const fn len(&self) -> usize {167		// TODO: use div_ceil168		let diff = self.to() - self.from();169		let rem = diff % self.step();170		let div = diff / self.step();171172		if rem == 0 {173			div174		} else {175			div + 1176		}177	}178}179180/// Represents a Jsonnet array value.181#[derive(Debug, Clone, Trace)]182// may contrain other ArrValue183#[trace(tracking(force))]184pub enum ArrValue {185	/// Layout optimized byte array.186	Bytes(#[trace(skip)] IBytes),187	/// Every element is lazy evaluated.188	Lazy(Cc<Vec<Thunk<Val>>>),189	/// Every field is already evaluated.190	Eager(Cc<Vec<Val>>),191	/// Concatenation of two arrays of any kind.192	Extended(Box<(Self, Self)>),193	/// Represents a integer array in form `[start, start + 1, ... end - 1, end]`.194	/// This kind of arrays is generated by `std.range(start, end)` call, and used for loops.195	Range(i32, i32),196	/// Sliced array view.197	Slice(Box<Slice>),198	/// Reversed array view.199	/// Returned by `std.reverse(other)` call200	Reversed(Box<Self>),201}202203#[cfg(target_pointer_width = "64")]204static_assertions::assert_eq_size!(ArrValue, [u8; 16]);205206impl ArrValue {207	pub fn new_eager() -> Self {208		Self::Eager(Cc::new(Vec::new()))209	}210	pub fn empty() -> Self {211		Self::new_range(0, 0)212	}213214	/// # Panics215	/// If a > b216	#[inline]217	pub fn new_range(a: i32, b: i32) -> Self {218		assert!(a <= b);219		Self::Range(a, b)220	}221222	/// # Panics223	/// If passed numbers are incorrect224	#[must_use]225	pub fn slice(self, from: Option<usize>, to: Option<usize>, step: Option<usize>) -> Self {226		let len = self.len();227		let from = from.unwrap_or(0);228		let to = to.unwrap_or(len).min(len);229		let step = step.unwrap_or(1);230		assert!(from < to);231		assert!(step > 0);232233		Self::Slice(Box::new(Slice {234			inner: self,235			from: from as u32,236			to: to as u32,237			step: step as u32,238		}))239	}240241	/// Array length.242	pub fn len(&self) -> usize {243		match self {244			Self::Bytes(i) => i.len(),245			Self::Lazy(l) => l.len(),246			Self::Eager(e) => e.len(),247			Self::Extended(v) => v.0.len() + v.1.len(),248			Self::Range(a, b) => a.abs_diff(*b) as usize + 1,249			Self::Reversed(i) => i.len(),250			Self::Slice(s) => s.len(),251		}252	}253254	/// Is array contains no elements?255	pub fn is_empty(&self) -> bool {256		self.len() == 0257	}258259	/// Get array element by index, evaluating it, if it is lazy.260	///261	/// Returns `None` on out-of-bounds condition.262	pub fn get(&self, index: usize) -> Result<Option<Val>> {263		match self {264			Self::Bytes(i) => i265				.get(index)266				.map_or(Ok(None), |v| Ok(Some(Val::Num(f64::from(*v))))),267			Self::Lazy(vec) => {268				if let Some(v) = vec.get(index) {269					Ok(Some(v.evaluate()?))270				} else {271					Ok(None)272				}273			}274			Self::Eager(vec) => Ok(vec.get(index).cloned()),275			Self::Extended(v) => {276				let a_len = v.0.len();277				if a_len > index {278					v.0.get(index)279				} else {280					v.1.get(index - a_len)281				}282			}283			Self::Range(a, _) => {284				if index >= self.len() {285					return Ok(None);286				}287				Ok(Some(Val::Num(((*a as isize) + index as isize) as f64)))288			}289			Self::Reversed(v) => {290				let len = v.len();291				if index >= len {292					return Ok(None);293				}294				v.get(len - index - 1)295			}296			Self::Slice(v) => {297				let index = v.from() + index * v.step();298				if index >= v.to() {299					return Ok(None);300				}301				v.inner.get(index)302			}303		}304	}305306	/// Get array element by index, without evaluation.307	///308	/// Returns `None` on out-of-bounds condition.309	pub fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {310		match self {311			Self::Bytes(i) => i312				.get(index)313				.map(|b| Thunk::evaluated(Val::Num(f64::from(*b)))),314			Self::Lazy(vec) => vec.get(index).cloned(),315			Self::Eager(vec) => vec.get(index).cloned().map(Thunk::evaluated),316			Self::Extended(v) => {317				let a_len = v.0.len();318				if a_len > index {319					v.0.get_lazy(index)320				} else {321					v.1.get_lazy(index - a_len)322				}323			}324			Self::Range(a, _) => {325				if index >= self.len() {326					return None;327				}328				Some(Thunk::evaluated(Val::Num(329					((*a as isize) + index as isize) as f64,330				)))331			}332			Self::Reversed(v) => {333				let len = v.len();334				if index >= len {335					return None;336				}337				v.get_lazy(len - index - 1)338			}339			Self::Slice(s) => {340				let index = s.from() + index * s.step();341				if index >= s.to() {342					return None;343				}344				s.inner.get_lazy(index)345			}346		}347	}348349	/// Evaluate all array elements, returning new array.350	pub fn evaluated(&self) -> Result<Cc<Vec<Val>>> {351		Ok(match self {352			Self::Bytes(i) => {353				let mut out = Vec::with_capacity(i.len());354				for v in i.iter() {355					out.push(Val::Num(f64::from(*v)));356				}357				Cc::new(out)358			}359			Self::Lazy(vec) => {360				let mut out = Vec::with_capacity(vec.len());361				for item in vec.iter() {362					out.push(item.evaluate()?);363				}364				Cc::new(out)365			}366			Self::Eager(vec) => vec.clone(),367			Self::Extended(_v) => {368				let mut out = Vec::with_capacity(self.len());369				for item in self.iter() {370					out.push(item?);371				}372				Cc::new(out)373			}374			Self::Range(a, b) => {375				let mut out = Vec::with_capacity(self.len());376				for i in *a..*b {377					out.push(Val::Num(f64::from(i)));378				}379				Cc::new(out)380			}381			Self::Reversed(r) => {382				let mut r = r.evaluated()?;383				Cc::update_with(&mut r, |v| v.reverse());384				r385			}386			Self::Slice(v) => {387				let mut out = Vec::with_capacity(v.inner.len());388				for v in v389					.inner390					.iter_lazy()391					.skip(v.from())392					.take(v.to() - v.from())393					.step_by(v.step())394				{395					out.push(v.evaluate()?);396				}397				Cc::new(out)398			}399		})400	}401402	/// Iterate over elements, evaluating them.403	pub fn iter(&self) -> impl DoubleEndedIterator<Item = Result<Val>> + '_ {404		(0..self.len()).map(move |idx| match self {405			Self::Bytes(b) => Ok(Val::Num(f64::from(b[idx]))),406			Self::Lazy(l) => l[idx].evaluate(),407			Self::Eager(e) => Ok(e[idx].clone()),408			Self::Extended(..) | Self::Range(..) | Self::Reversed(..) | Self::Slice(..) => {409				self.get(idx).map(|e| e.expect("idx < len"))410			}411		})412	}413414	/// Iterate over elements, returning lazy values.415	pub fn iter_lazy(&self) -> impl DoubleEndedIterator<Item = Thunk<Val>> + '_ {416		(0..self.len()).map(move |idx| match self {417			Self::Bytes(b) => Thunk::evaluated(Val::Num(f64::from(b[idx]))),418			Self::Lazy(l) => l[idx].clone(),419			Self::Eager(e) => Thunk::evaluated(e[idx].clone()),420			Self::Slice(..) | Self::Extended(..) | Self::Range(..) | Self::Reversed(..) => {421				self.get_lazy(idx).expect("idx < len")422			}423		})424	}425426	/// Return a reversed view on current array.427	#[must_use]428	pub fn reversed(self) -> Self {429		Self::Reversed(Box::new(self))430	}431432	/// Return a new array, produced by passing every element of current array to specified callback function.433	pub fn map(self, mapper: impl Fn(Val) -> Result<Val>) -> Result<Self> {434		let mut out = Vec::with_capacity(self.len());435436		for value in self.iter() {437			out.push(mapper(value?)?);438		}439440		Ok(Self::Eager(Cc::new(out)))441	}442443	/// Return a new array, produced from current array by removing every value, for which specified callback function returns false.444	pub fn filter(self, filter: impl Fn(&Val) -> Result<bool>) -> Result<Self> {445		let mut out = Vec::with_capacity(self.len());446447		for value in self.iter() {448			let value = value?;449			if filter(&value)? {450				out.push(value);451			}452		}453454		Ok(Self::Eager(Cc::new(out)))455	}456457	pub fn ptr_eq(a: &Self, b: &Self) -> bool {458		match (a, b) {459			(Self::Lazy(a), Self::Lazy(b)) => Cc::ptr_eq(a, b),460			(Self::Eager(a), Self::Eager(b)) => Cc::ptr_eq(a, b),461			_ => false,462		}463	}464}465466impl From<Vec<Thunk<Val>>> for ArrValue {467	fn from(v: Vec<Thunk<Val>>) -> Self {468		Self::Lazy(Cc::new(v))469	}470}471472impl From<Vec<Val>> for ArrValue {473	fn from(v: Vec<Val>) -> Self {474		Self::Eager(Cc::new(v))475	}476}477478/// Represents a Jsonnet value, which can be spliced or indexed (string or array).479#[allow(clippy::module_name_repetitions)]480pub enum IndexableVal {481	/// String.482	Str(IStr),483	/// Array.484	Arr(ArrValue),485}486impl IndexableVal {487	/// Slice the value.488	///489	/// # Implementation490	///491	/// For strings, will create a copy of specified interval.492	///493	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.494	pub fn slice(495		self,496		index: Option<BoundedUsize<0, { i32::MAX as usize }>>,497		end: Option<BoundedUsize<0, { i32::MAX as usize }>>,498		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,499	) -> Result<Self> {500		match &self {501			IndexableVal::Str(s) => {502				let index = index.as_deref().copied().unwrap_or(0);503				let end = end.as_deref().copied().unwrap_or(usize::MAX);504				let step = step.as_deref().copied().unwrap_or(1);505506				if index >= end {507					return Ok(Self::Str("".into()));508				}509510				Ok(Self::Str(511					(s.chars()512						.skip(index)513						.take(end - index)514						.step_by(step)515						.collect::<String>())516					.into(),517				))518			}519			IndexableVal::Arr(arr) => {520				let index = index.as_deref().copied().unwrap_or(0);521				let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());522				let step = step.as_deref().copied().unwrap_or(1);523524				if index >= end {525					return Ok(Self::Arr(ArrValue::new_eager()));526				}527528				Ok(Self::Arr(ArrValue::Slice(Box::new(Slice {529					inner: arr.clone(),530					from: index as u32,531					to: end as u32,532					step: step as u32,533				}))))534			}535		}536	}537}538539/// Represents any valid Jsonnet value.540#[derive(Debug, Clone, Trace)]541pub enum Val {542	/// Represents a Jsonnet boolean.543	Bool(bool),544	/// Represents a Jsonnet null value.545	Null,546	/// Represents a Jsonnet string.547	Str(IStr),548	/// Represents a Jsonnet number.549	/// Should be finite, and not NaN550	/// This restriction isn't enforced by enum, as enum field can't be marked as private551	Num(f64),552	/// Represents a Jsonnet array.553	Arr(ArrValue),554	/// Represents a Jsonnet object.555	Obj(ObjValue),556	/// Represents a Jsonnet function.557	Func(FuncVal),558}559560impl From<IndexableVal> for Val {561	fn from(v: IndexableVal) -> Self {562		match v {563			IndexableVal::Str(s) => Self::Str(s),564			IndexableVal::Arr(a) => Self::Arr(a),565		}566	}567}568569// Broken between stable and nightly, as there is new layout size optimization570// #[cfg(target_pointer_width = "64")]571// static_assertions::assert_eq_size!(Val, [u8; 24]);572573impl Val {574	pub const fn as_bool(&self) -> Option<bool> {575		match self {576			Self::Bool(v) => Some(*v),577			_ => None,578		}579	}580	pub const fn as_null(&self) -> Option<()> {581		match self {582			Self::Null => Some(()),583			_ => None,584		}585	}586	pub fn as_str(&self) -> Option<IStr> {587		match self {588			Self::Str(s) => Some(s.clone()),589			_ => None,590		}591	}592	pub const fn as_num(&self) -> Option<f64> {593		match self {594			Self::Num(n) => Some(*n),595			_ => None,596		}597	}598	pub fn as_arr(&self) -> Option<ArrValue> {599		match self {600			Self::Arr(a) => Some(a.clone()),601			_ => None,602		}603	}604	pub fn as_obj(&self) -> Option<ObjValue> {605		match self {606			Self::Obj(o) => Some(o.clone()),607			_ => None,608		}609	}610	pub fn as_func(&self) -> Option<FuncVal> {611		match self {612			Self::Func(f) => Some(f.clone()),613			_ => None,614		}615	}616617	/// Creates `Val::Num` after checking for numeric overflow.618	/// As numbers are `f64`, we can just check for their finity.619	pub fn new_checked_num(num: f64) -> Result<Self> {620		if num.is_finite() {621			Ok(Self::Num(num))622		} else {623			throw!("overflow")624		}625	}626627	pub const fn value_type(&self) -> ValType {628		match self {629			Self::Str(..) => ValType::Str,630			Self::Num(..) => ValType::Num,631			Self::Arr(..) => ValType::Arr,632			Self::Obj(..) => ValType::Obj,633			Self::Bool(_) => ValType::Bool,634			Self::Null => ValType::Null,635			Self::Func(..) => ValType::Func,636		}637	}638639	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {640		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {641			manifest.manifest(val.clone())642		}643		manifest_dyn(self, &format)644	}645646	pub fn to_string(&self) -> Result<IStr> {647		Ok(match self {648			Self::Bool(true) => "true".into(),649			Self::Bool(false) => "false".into(),650			Self::Null => "null".into(),651			Self::Str(s) => s.clone(),652			_ => self653				.manifest(crate::stdlib::manifest::ToStringFormat)654				.map(IStr::from)?,655		})656	}657658	pub fn into_indexable(self) -> Result<IndexableVal> {659		Ok(match self {660			Val::Str(s) => IndexableVal::Str(s),661			Val::Arr(arr) => IndexableVal::Arr(arr),662			_ => throw!(ValueIsNotIndexable(self.value_type())),663		})664	}665}666667const fn is_function_like(val: &Val) -> bool {668	matches!(val, Val::Func(_))669}670671/// Native implementation of `std.primitiveEquals`672pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {673	Ok(match (val_a, val_b) {674		(Val::Bool(a), Val::Bool(b)) => a == b,675		(Val::Null, Val::Null) => true,676		(Val::Str(a), Val::Str(b)) => a == b,677		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,678		(Val::Arr(_), Val::Arr(_)) => {679			throw!("primitiveEquals operates on primitive types, got array")680		}681		(Val::Obj(_), Val::Obj(_)) => {682			throw!("primitiveEquals operates on primitive types, got object")683		}684		(a, b) if is_function_like(a) && is_function_like(b) => {685			throw!("cannot test equality of functions")686		}687		(_, _) => false,688	})689}690691/// Native implementation of `std.equals`692pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {693	if val_a.value_type() != val_b.value_type() {694		return Ok(false);695	}696	match (val_a, val_b) {697		(Val::Arr(a), Val::Arr(b)) => {698			if ArrValue::ptr_eq(a, b) {699				return Ok(true);700			}701			if a.len() != b.len() {702				return Ok(false);703			}704			for (a, b) in a.iter().zip(b.iter()) {705				if !equals(&a?, &b?)? {706					return Ok(false);707				}708			}709			Ok(true)710		}711		(Val::Obj(a), Val::Obj(b)) => {712			if ObjValue::ptr_eq(a, b) {713				return Ok(true);714			}715			let fields = a.fields(716				#[cfg(feature = "exp-preserve-order")]717				false,718			);719			if fields720				!= b.fields(721					#[cfg(feature = "exp-preserve-order")]722					false,723				) {724				return Ok(false);725			}726			for field in fields {727				if !equals(728					&a.get(field.clone())?.expect("field exists"),729					&b.get(field)?.expect("field exists"),730				)? {731					return Ok(false);732				}733			}734			Ok(true)735		}736		(a, b) => Ok(primitive_equals(a, b)?),737	}738}
modifiedcrates/jrsonnet-stdlib/src/manifest.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/manifest.rs
+++ b/crates/jrsonnet-stdlib/src/manifest.rs
@@ -1,10 +1,7 @@
 use jrsonnet_evaluator::{
 	error::Result,
 	function::builtin,
-	stdlib::manifest::{
-		escape_string_json, manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType,
-		ManifestYamlOptions,
-	},
+	stdlib::manifest::{escape_string_json, JsonFormat, YamlFormat},
 	typed::Any,
 	IStr,
 };
@@ -24,17 +21,13 @@
 ) -> Result<String> {
 	let newline = newline.as_deref().unwrap_or("\n");
 	let key_val_sep = key_val_sep.as_deref().unwrap_or(": ");
-	manifest_json_ex(
-		&value.0,
-		&ManifestJsonOptions {
-			padding: &indent,
-			mtype: ManifestType::Std,
-			newline,
-			key_val_sep,
-			#[cfg(feature = "exp-preserve-order")]
-			preserve_order: preserve_order.unwrap_or(false),
-		},
-	)
+	value.0.manifest(JsonFormat::std_to_json(
+		indent.to_string(),
+		newline,
+		key_val_sep,
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order.unwrap_or(false),
+	))
 }
 
 #[builtin]
@@ -44,18 +37,10 @@
 	quote_keys: Option<bool>,
 	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
 ) -> Result<String> {
-	manifest_yaml_ex(
-		&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),
-		},
-	)
+	value.0.manifest(YamlFormat::std_to_yaml(
+		indent_array_in_object.unwrap_or(false),
+		quote_keys.unwrap_or(true),
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order.unwrap_or(false),
+	))
 }