git.delta.rocks / jrsonnet / refs/commits / d76beb28c515

difftreelog

feat object destructuring defaults

Yaroslav Bolyukin2022-06-03parent: #3961a53.patch.diff
in: master

5 files changed

modifiedcrates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -8,6 +8,9 @@
 	pub fn new() -> Self {
 		Self(Cc::new(RefCell::new(None)))
 	}
+	pub fn new_filled(v: T) -> Self {
+		Self(Cc::new(RefCell::new(Some(v))))
+	}
 	/// # Panics
 	/// If wrapper is filled already
 	pub fn fill(self, value: T) {
modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/evaluate/destructure.rs
1use gcmodule::Trace;2use jrsonnet_interner::IStr;3use jrsonnet_parser::{BindSpec, Destruct, LocExpr, ParamsDesc};45use crate::{6	error::{Error::*, Result},7	evaluate, evaluate_method, evaluate_named,8	gc::GcHashMap,9	tb, throw,10	val::ThunkValue,11	Context, Pending, State, Thunk, Val,12};1314#[allow(clippy::too_many_lines)]15pub fn destruct(16	d: &Destruct,17	parent: Thunk<Val>,18	new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,19) -> Result<()> {20	match d {21		Destruct::Full(v) => {22			let old = new_bindings.insert(v.clone(), parent);23			if old.is_some() {24				throw!(DuplicateLocalVar(v.clone()))25			}26		}27		#[cfg(feature = "exp-destruct")]28		Destruct::Skip => {}29		#[cfg(feature = "exp-destruct")]30		Destruct::Array { start, rest, end } => {31			use jrsonnet_parser::DestructRest;3233			use crate::{throw_runtime, val::ArrValue};3435			#[derive(Trace)]36			struct DataThunk {37				parent: Thunk<Val>,38				min_len: usize,39				has_rest: bool,40			}41			impl ThunkValue for DataThunk {42				type Output = ArrValue;4344				fn get(self: Box<Self>, s: State) -> Result<Self::Output> {45					let v = self.parent.evaluate(s)?;46					let arr = match v {47						Val::Arr(a) => a,48						_ => throw_runtime!("expected array"),49					};50					if !self.has_rest {51						if arr.len() != self.min_len {52							throw_runtime!("expected {} elements, got {}", self.min_len, arr.len())53						}54					} else if arr.len() < self.min_len {55						throw_runtime!(56							"expected at least {} elements, but array was only {}",57							self.min_len,58							arr.len()59						)60					}61					Ok(arr)62				}63			}6465			let full = Thunk::new(tb!(DataThunk {66				min_len: start.len() + end.len(),67				has_rest: rest.is_some(),68				parent,69			}));7071			{72				#[derive(Trace)]73				struct BaseThunk {74					full: Thunk<ArrValue>,75					index: usize,76				}77				impl ThunkValue for BaseThunk {78					type Output = Val;7980					fn get(self: Box<Self>, s: State) -> Result<Self::Output> {81						let full = self.full.evaluate(s.clone())?;82						Ok(full.get(s, self.index)?.expect("length is checked"))83					}84				}85				for (i, d) in start.iter().enumerate() {86					destruct(87						d,88						Thunk::new(tb!(BaseThunk {89							full: full.clone(),90							index: i,91						})),92						new_bindings,93					)?;94				}95			}9697			match rest {98				Some(DestructRest::Keep(v)) => {99					#[derive(Trace)]100					struct RestThunk {101						full: Thunk<ArrValue>,102						start: usize,103						end: usize,104					}105					impl ThunkValue for RestThunk {106						type Output = Val;107108						fn get(self: Box<Self>, s: State) -> Result<Self::Output> {109							let full = self.full.evaluate(s)?;110							let to = full.len() - self.end;111							Ok(Val::Arr(full.slice(Some(self.start), Some(to), None)))112						}113					}114115					destruct(116						&Destruct::Full(v.clone()),117						Thunk::new(tb!(RestThunk {118							full: full.clone(),119							start: start.len(),120							end: end.len(),121						})),122						new_bindings,123					)?;124				}125				Some(DestructRest::Drop) => {}126				None => {}127			}128129			{130				#[derive(Trace)]131				struct EndThunk {132					full: Thunk<ArrValue>,133					index: usize,134					end: usize,135				}136				impl ThunkValue for EndThunk {137					type Output = Val;138139					fn get(self: Box<Self>, s: State) -> Result<Self::Output> {140						let full = self.full.evaluate(s.clone())?;141						Ok(full142							.get(s, full.len() - self.end + self.index)?143							.expect("length is checked"))144					}145				}146				for (i, d) in end.iter().enumerate() {147					destruct(148						d,149						Thunk::new(tb!(EndThunk {150							full: full.clone(),151							index: i,152							end: end.len(),153						})),154						new_bindings,155					)?;156				}157			}158		}159		#[cfg(feature = "exp-destruct")]160		Destruct::Object { fields, rest } => {161			use crate::{obj::ObjValue, throw_runtime};162163			#[derive(Trace)]164			struct DataThunk {165				parent: Thunk<Val>,166				field_names: Vec<IStr>,167				has_rest: bool,168			}169			impl ThunkValue for DataThunk {170				type Output = ObjValue;171172				fn get(self: Box<Self>, s: State) -> Result<Self::Output> {173					let v = self.parent.evaluate(s)?;174					let obj = match v {175						Val::Obj(o) => o,176						_ => throw_runtime!("expected object"),177					};178					for field in &self.field_names {179						if !obj.has_field_ex(field.clone(), true) {180							throw_runtime!("missing field: {}", field);181						}182					}183					if !self.has_rest {184						let len = obj.len();185						if len != self.field_names.len() {186							throw_runtime!("too many fields, and rest not found");187						}188					}189					Ok(obj)190				}191			}192			let field_names: Vec<_> = fields.iter().map(|f| f.0.clone()).collect();193			let full = Thunk::new(tb!(DataThunk {194				parent,195				field_names: field_names.clone(),196				has_rest: rest.is_some()197			}));198199			for (field, d) in fields {200				#[derive(Trace)]201				struct FieldThunk {202					full: Thunk<ObjValue>,203					field: IStr,204				}205				impl ThunkValue for FieldThunk {206					type Output = Val;207208					fn get(self: Box<Self>, s: State) -> Result<Self::Output> {209						let full = self.full.evaluate(s.clone())?;210						let field = full.get(s, self.field)?.expect("shape is checked");211						Ok(field)212					}213				}214				let value = Thunk::new(tb!(FieldThunk {215					full: full.clone(),216					field: field.clone()217				}));218				if let Some(d) = d {219					destruct(d, value, new_bindings)?;220				} else {221					destruct(&Destruct::Full(field.clone()), value, new_bindings)?;222				}223			}224		}225	}226	Ok(())227}228229pub fn evaluate_dest(230	d: &BindSpec,231	fctx: Pending<Context>,232	new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,233) -> Result<()> {234	match d {235		BindSpec::Field { into, value } => {236			#[derive(Trace)]237			struct EvaluateThunkValue {238				name: Option<IStr>,239				fctx: Pending<Context>,240				expr: LocExpr,241			}242			impl ThunkValue for EvaluateThunkValue {243				type Output = Val;244				fn get(self: Box<Self>, s: State) -> Result<Self::Output> {245					if let Some(name) = self.name {246						evaluate_named(s, self.fctx.unwrap(), &self.expr, name)247					} else {248						evaluate(s, self.fctx.unwrap(), &self.expr)249					}250				}251			}252			let data = Thunk::new(tb!(EvaluateThunkValue {253				name: into.name(),254				fctx,255				expr: value.clone(),256			}));257			destruct(into, data, new_bindings)?;258		}259		BindSpec::Function {260			name,261			params,262			value,263		} => {264			#[derive(Trace)]265			struct MethodThunk {266				fctx: Pending<Context>,267				name: IStr,268				params: ParamsDesc,269				value: LocExpr,270			}271			impl ThunkValue for MethodThunk {272				type Output = Val;273274				fn get(self: Box<Self>, _s: State) -> Result<Self::Output> {275					Ok(evaluate_method(276						self.fctx.unwrap(),277						self.name,278						self.params,279						self.value,280					))281				}282			}283284			let old = new_bindings.insert(285				name.clone(),286				Thunk::new(tb!(MethodThunk {287					fctx,288					name: name.clone(),289					params: params.clone(),290					value: value.clone()291				})),292			);293			if old.is_some() {294				throw!(DuplicateLocalVar(name.clone()))295			}296		}297	}298	Ok(())299}
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -56,7 +56,12 @@
 
 	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {
 		let name = params[id].0.clone();
-		destruct(&name, arg, &mut passed_args)?;
+		destruct(
+			&name,
+			arg,
+			Pending::new_filled(ctx.clone()),
+			&mut passed_args,
+		)?;
 		filled_positionals += 1;
 		Ok(())
 	})?;
@@ -96,6 +101,7 @@
 					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),
 					value: param.1.clone().expect("default exists"),
 				})),
+				fctx.clone(),
 				&mut defaults,
 			)?;
 			if param.0.name().is_some() {
@@ -230,6 +236,7 @@
 					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),
 					value: v.clone(),
 				})),
+				fctx.clone(),
 				&mut bindings,
 			)?;
 		} else {
@@ -238,6 +245,7 @@
 				Thunk::new(tb!(DependsOnUnbound(
 					param.0.name().unwrap_or_else(|| "<destruct>".into())
 				))),
+				fctx.clone(),
 				&mut bindings,
 			)?;
 		}
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -188,7 +188,7 @@
 }
 
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-#[derive(Debug, Clone, PartialEq, Eq, Trace)]
+#[derive(Debug, Clone, PartialEq, Trace)]
 pub enum Destruct {
 	Full(IStr),
 	#[cfg(feature = "exp-destruct")]
@@ -201,7 +201,7 @@
 	},
 	#[cfg(feature = "exp-destruct")]
 	Object {
-		fields: Vec<(IStr, Option<Destruct>)>,
+		fields: Vec<(IStr, Option<Destruct>, Option<LocExpr>)>,
 		rest: Option<DestructRest>,
 	},
 }
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -1,4 +1,4 @@
-#![allow(clippy::redundant_closure_call)]
+#![allow(clippy::redundant_closure_call, clippy::derive_partial_eq_without_eq)]
 
 use std::rc::Rc;
 
@@ -109,7 +109,7 @@
 			}
 		pub rule destruct_object(s: &ParserSettings) -> expr::Destruct
 			= "{" _
-				fields:(name:id() _ into:(":" _ into:destruct(s) {into})? {(name, into)})**comma()
+				fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:expr(s) {v})? {(name, into, default)})**comma()
 				rest:(
 					comma() rest:destruct_rest()? {rest}
 					/ comma()? {None}