git.delta.rocks / jrsonnet / refs/commits / 94b99170eb24

difftreelog

perf add string extension threshold

Yaroslav Bolyukin2022-12-03parent: #54c4db5.patch.diff
in: master

2 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -24,13 +24,9 @@
 	})
 }
 
-/// Arbitrary threshold
-
 pub fn evaluate_add_op(a: &Val, b: &Val) -> Result<Val> {
 	use Val::*;
 	Ok(match (a, b) {
-		(Str(a), Str(b)) if a.is_empty() => Val::Str(b.clone()),
-		(Str(a), Str(b)) if b.is_empty() => Val::Str(a.clone()),
 		(Str(v1), Str(v2)) => Str(StrValue::concat(v1.clone(), v2.clone())),
 
 		// Can't use generic json serialization way, because it depends on number to string concatenation (std.jsonnet:890)
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
1use std::{2	cell::RefCell,3	fmt::{self, Debug, Display},4	mem::replace,5	rc::Rc,6};78use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_interner::IStr;10use jrsonnet_types::ValType;1112pub use crate::arr::ArrValue;13use crate::{14	error::{Error, ErrorKind::*},15	function::FuncVal,16	gc::{GcHashMap, TraceBox},17	manifest::{ManifestFormat, ToStringFormat},18	throw,19	typed::BoundedUsize,20	ObjValue, Result, Unbound, WeakObjValue,21};2223pub trait ThunkValue: Trace {24	type Output;25	fn get(self: Box<Self>) -> Result<Self::Output>;26}2728#[derive(Trace)]29enum ThunkInner<T: Trace> {30	Computed(T),31	Errored(Error),32	Waiting(TraceBox<dyn ThunkValue<Output = T>>),33	Pending,34}3536#[allow(clippy::module_name_repetitions)]37#[derive(Clone, Trace)]38pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);3940impl<T: Trace> Thunk<T> {41	pub fn evaluated(val: T) -> Self {42		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))43	}44	pub fn new(f: TraceBox<dyn ThunkValue<Output = T>>) -> Self {45		Self(Cc::new(RefCell::new(ThunkInner::Waiting(f))))46	}47	pub fn errored(e: Error) -> Self {48		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))49	}50}5152impl<T> Thunk<T>53where54	T: Clone + Trace,55{56	pub fn force(&self) -> Result<()> {57		self.evaluate()?;58		Ok(())59	}60	pub fn evaluate(&self) -> Result<T> {61		match &*self.0.borrow() {62			ThunkInner::Computed(v) => return Ok(v.clone()),63			ThunkInner::Errored(e) => return Err(e.clone()),64			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),65			ThunkInner::Waiting(..) => (),66		};67		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) else {68			unreachable!();69		};70		let new_value = match value.0.get() {71			Ok(v) => v,72			Err(e) => {73				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());74				return Err(e);75			}76		};77		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());78		Ok(new_value)79	}80}8182type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);8384#[derive(Trace, Clone)]85pub struct CachedUnbound<I, T>86where87	I: Unbound<Bound = T>,88	T: Trace,89{90	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,91	value: I,92}93impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {94	pub fn new(value: I) -> Self {95		Self {96			cache: Cc::new(RefCell::new(GcHashMap::new())),97			value,98		}99	}100}101impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {102	type Bound = T;103	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {104		let cache_key = (105			sup.as_ref().map(|s| s.clone().downgrade()),106			this.as_ref().map(|t| t.clone().downgrade()),107		);108		{109			if let Some(t) = self.cache.borrow().get(&cache_key) {110				return Ok(t.clone());111			}112		}113		let bound = self.value.bind(sup, this)?;114115		{116			let mut cache = self.cache.borrow_mut();117			cache.insert(cache_key, bound.clone());118		}119120		Ok(bound)121	}122}123124impl<T: Debug + Trace> Debug for Thunk<T> {125	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {126		write!(f, "Lazy")127	}128}129impl<T: Trace> PartialEq for Thunk<T> {130	fn eq(&self, other: &Self) -> bool {131		Cc::ptr_eq(&self.0, &other.0)132	}133}134135/// Represents a Jsonnet value, which can be spliced or indexed (string or array).136#[allow(clippy::module_name_repetitions)]137pub enum IndexableVal {138	/// String.139	Str(IStr),140	/// Array.141	Arr(ArrValue),142}143impl IndexableVal {144	/// Slice the value.145	///146	/// # Implementation147	///148	/// For strings, will create a copy of specified interval.149	///150	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.151	pub fn slice(152		self,153		index: Option<BoundedUsize<0, { i32::MAX as usize }>>,154		end: Option<BoundedUsize<0, { i32::MAX as usize }>>,155		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,156	) -> Result<Self> {157		match &self {158			IndexableVal::Str(s) => {159				let index = index.as_deref().copied().unwrap_or(0);160				let end = end.as_deref().copied().unwrap_or(usize::MAX);161				let step = step.as_deref().copied().unwrap_or(1);162163				if index >= end {164					return Ok(Self::Str("".into()));165				}166167				Ok(Self::Str(168					(s.chars()169						.skip(index)170						.take(end - index)171						.step_by(step)172						.collect::<String>())173					.into(),174				))175			}176			IndexableVal::Arr(arr) => {177				let index = index.as_deref().copied().unwrap_or(0);178				let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());179				let step = step.as_deref().copied().unwrap_or(1);180181				if index >= end {182					return Ok(Self::Arr(ArrValue::empty()));183				}184185				Ok(Self::Arr(186					arr.clone()187						.slice(Some(index), Some(end), Some(step))188						.expect("arguments checked"),189				))190			}191		}192	}193}194195#[derive(Debug, Clone, Trace)]196pub enum StrValue {197	Flat(IStr),198	Tree(Rc<(StrValue, StrValue, usize)>),199}200impl StrValue {201	pub fn concat(a: StrValue, b: StrValue) -> Self {202		if a.is_empty() {203			b204		} else if b.is_empty() {205			a206		} else {207			let len = a.len() + b.len();208			Self::Tree(Rc::new((a, b, len)))209		}210	}211	pub fn into_flat(self) -> IStr {212		match self {213			StrValue::Flat(f) => f,214			StrValue::Tree(_) => {215				let mut buf = String::new();216				self.into_flat_buf(&mut buf);217				buf.into()218			}219		}220	}221	fn into_flat_buf(&self, out: &mut String) {222		match self {223			StrValue::Flat(f) => out.push_str(f),224			StrValue::Tree(t) => {225				t.0.into_flat_buf(out);226				t.1.into_flat_buf(out);227			}228		}229	}230	pub fn len(&self) -> usize {231		match self {232			StrValue::Flat(v) => v.len(),233			StrValue::Tree(t) => t.2,234		}235	}236	pub fn is_empty(&self) -> bool {237		match self {238			Self::Flat(v) => v.is_empty(),239			_ => false,240		}241	}242}243impl Display for StrValue {244	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {245		match self {246			StrValue::Flat(v) => write!(f, "{v}"),247			StrValue::Tree(t) => {248				write!(f, "{}", t.0)?;249				write!(f, "{}", t.1)250			}251		}252	}253}254impl PartialEq for StrValue {255	fn eq(&self, other: &Self) -> bool {256		let a = self.clone().into_flat();257		let b = other.clone().into_flat();258		a == b259	}260}261impl Eq for StrValue {}262impl PartialOrd for StrValue {263	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {264		let a = self.clone().into_flat();265		let b = other.clone().into_flat();266		Some(a.cmp(&b))267	}268}269impl Ord for StrValue {270	fn cmp(&self, other: &Self) -> std::cmp::Ordering {271		self.partial_cmp(other)272			.expect("partial_cmp always returns Some")273	}274}275276/// Represents any valid Jsonnet value.277#[derive(Debug, Clone, Trace)]278pub enum Val {279	/// Represents a Jsonnet boolean.280	Bool(bool),281	/// Represents a Jsonnet null value.282	Null,283	/// Represents a Jsonnet string.284	Str(StrValue),285	/// Represents a Jsonnet number.286	/// Should be finite, and not NaN287	/// This restriction isn't enforced by enum, as enum field can't be marked as private288	Num(f64),289	/// Represents a Jsonnet array.290	Arr(ArrValue),291	/// Represents a Jsonnet object.292	Obj(ObjValue),293	/// Represents a Jsonnet function.294	Func(FuncVal),295}296297static_assertions::assert_eq_size!(Val, [u8; 24]);298299impl From<IndexableVal> for Val {300	fn from(v: IndexableVal) -> Self {301		match v {302			IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),303			IndexableVal::Arr(a) => Self::Arr(a),304		}305	}306}307308impl Val {309	pub const fn as_bool(&self) -> Option<bool> {310		match self {311			Self::Bool(v) => Some(*v),312			_ => None,313		}314	}315	pub const fn as_null(&self) -> Option<()> {316		match self {317			Self::Null => Some(()),318			_ => None,319		}320	}321	pub fn as_str(&self) -> Option<IStr> {322		match self {323			Self::Str(s) => Some(s.clone().into_flat()),324			_ => None,325		}326	}327	pub const fn as_num(&self) -> Option<f64> {328		match self {329			Self::Num(n) => Some(*n),330			_ => None,331		}332	}333	pub fn as_arr(&self) -> Option<ArrValue> {334		match self {335			Self::Arr(a) => Some(a.clone()),336			_ => None,337		}338	}339	pub fn as_obj(&self) -> Option<ObjValue> {340		match self {341			Self::Obj(o) => Some(o.clone()),342			_ => None,343		}344	}345	pub fn as_func(&self) -> Option<FuncVal> {346		match self {347			Self::Func(f) => Some(f.clone()),348			_ => None,349		}350	}351352	/// Creates `Val::Num` after checking for numeric overflow.353	/// As numbers are `f64`, we can just check for their finity.354	pub fn new_checked_num(num: f64) -> Result<Self> {355		if num.is_finite() {356			Ok(Self::Num(num))357		} else {358			throw!("overflow")359		}360	}361362	pub const fn value_type(&self) -> ValType {363		match self {364			Self::Str(..) => ValType::Str,365			Self::Num(..) => ValType::Num,366			Self::Arr(..) => ValType::Arr,367			Self::Obj(..) => ValType::Obj,368			Self::Bool(_) => ValType::Bool,369			Self::Null => ValType::Null,370			Self::Func(..) => ValType::Func,371		}372	}373374	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {375		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {376			manifest.manifest(val.clone())377		}378		manifest_dyn(self, &format)379	}380381	pub fn to_string(&self) -> Result<IStr> {382		Ok(match self {383			Self::Bool(true) => "true".into(),384			Self::Bool(false) => "false".into(),385			Self::Null => "null".into(),386			Self::Str(s) => s.clone().into_flat(),387			_ => self.manifest(ToStringFormat).map(IStr::from)?,388		})389	}390391	pub fn into_indexable(self) -> Result<IndexableVal> {392		Ok(match self {393			Val::Str(s) => IndexableVal::Str(s.into_flat()),394			Val::Arr(arr) => IndexableVal::Arr(arr),395			_ => throw!(ValueIsNotIndexable(self.value_type())),396		})397	}398}399400const fn is_function_like(val: &Val) -> bool {401	matches!(val, Val::Func(_))402}403404/// Native implementation of `std.primitiveEquals`405pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {406	Ok(match (val_a, val_b) {407		(Val::Bool(a), Val::Bool(b)) => a == b,408		(Val::Null, Val::Null) => true,409		(Val::Str(a), Val::Str(b)) => a == b,410		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,411		(Val::Arr(_), Val::Arr(_)) => {412			throw!("primitiveEquals operates on primitive types, got array")413		}414		(Val::Obj(_), Val::Obj(_)) => {415			throw!("primitiveEquals operates on primitive types, got object")416		}417		(a, b) if is_function_like(a) && is_function_like(b) => {418			throw!("cannot test equality of functions")419		}420		(_, _) => false,421	})422}423424/// Native implementation of `std.equals`425pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {426	if val_a.value_type() != val_b.value_type() {427		return Ok(false);428	}429	match (val_a, val_b) {430		(Val::Arr(a), Val::Arr(b)) => {431			if ArrValue::ptr_eq(a, b) {432				return Ok(true);433			}434			if a.len() != b.len() {435				return Ok(false);436			}437			for (a, b) in a.iter().zip(b.iter()) {438				if !equals(&a?, &b?)? {439					return Ok(false);440				}441			}442			Ok(true)443		}444		(Val::Obj(a), Val::Obj(b)) => {445			if ObjValue::ptr_eq(a, b) {446				return Ok(true);447			}448			let fields = a.fields(449				#[cfg(feature = "exp-preserve-order")]450				false,451			);452			if fields453				!= b.fields(454					#[cfg(feature = "exp-preserve-order")]455					false,456				) {457				return Ok(false);458			}459			for field in fields {460				if !equals(461					&a.get(field.clone())?.expect("field exists"),462					&b.get(field)?.expect("field exists"),463				)? {464					return Ok(false);465				}466			}467			Ok(true)468		}469		(a, b) => Ok(primitive_equals(a, b)?),470	}471}