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

difftreelog

source

crates/jrsonnet-evaluator/src/evaluate/destructure.rs6.9 KiBsourcehistory
1use jrsonnet_gcmodule::Trace;2use jrsonnet_interner::IStr;3use jrsonnet_parser::{BindSpec, Destruct, LocExpr, ParamsDesc};45use crate::{6	bail,7	error::{ErrorKind::*, Result},8	evaluate, evaluate_method, evaluate_named,9	gc::GcHashMap,10	val::ThunkValue,11	Context, Pending, Thunk, Val,12};1314#[allow(clippy::too_many_lines)]15#[allow(unused_variables)]16pub fn destruct(17	d: &Destruct,18	parent: Thunk<Val>,19	fctx: Pending<Context>,20	new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,21) -> Result<()> {22	match d {23		Destruct::Full(v) => {24			let old = new_bindings.insert(v.clone(), parent);25			if old.is_some() {26				bail!(DuplicateLocalVar(v.clone()))27			}28		}29		#[cfg(feature = "exp-destruct")]30		Destruct::Skip => {}31		#[cfg(feature = "exp-destruct")]32		Destruct::Array { start, rest, end } => {33			use jrsonnet_parser::DestructRest;3435			use crate::arr::ArrValue;3637			#[derive(Trace)]38			struct DataThunk {39				parent: Thunk<Val>,40				min_len: usize,41				has_rest: bool,42			}43			impl ThunkValue for DataThunk {44				type Output = ArrValue;4546				fn get(self: Box<Self>) -> Result<Self::Output> {47					let v = self.parent.evaluate()?;48					let Val::Arr(arr) = v else {49						bail!("expected array");50					};51					if !self.has_rest {52						if arr.len() != self.min_len {53							bail!("expected {} elements, got {}", self.min_len, arr.len())54						}55					} else if arr.len() < self.min_len {56						bail!(57							"expected at least {} elements, but array was only {}",58							self.min_len,59							arr.len()60						)61					}62					Ok(arr)63				}64			}6566			let full = Thunk::new(DataThunk {67				min_len: start.len() + end.len(),68				has_rest: rest.is_some(),69				parent,70			});7172			{73				#[derive(Trace)]74				struct BaseThunk {75					full: Thunk<ArrValue>,76					index: usize,77				}78				impl ThunkValue for BaseThunk {79					type Output = Val;8081					fn get(self: Box<Self>) -> Result<Self::Output> {82						let full = self.full.evaluate()?;83						Ok(full.get(self.index)?.expect("length is checked"))84					}85				}86				for (i, d) in start.iter().enumerate() {87					destruct(88						d,89						Thunk::new(BaseThunk {90							full: full.clone(),91							index: i,92						}),93						fctx.clone(),94						new_bindings,95					)?;96				}97			}9899			match rest {100				Some(DestructRest::Keep(v)) => {101					#[derive(Trace)]102					struct RestThunk {103						full: Thunk<ArrValue>,104						start: usize,105						end: usize,106					}107					impl ThunkValue for RestThunk {108						type Output = Val;109110						fn get(self: Box<Self>) -> Result<Self::Output> {111							let full = self.full.evaluate()?;112							let to = full.len() - self.end;113							Ok(Val::Arr(114								full.slice(Some(self.start), Some(to), None)115									.expect("arguments checked"),116							))117						}118					}119120					destruct(121						&Destruct::Full(v.clone()),122						Thunk::new(RestThunk {123							full: full.clone(),124							start: start.len(),125							end: end.len(),126						}),127						fctx.clone(),128						new_bindings,129					)?;130				}131				Some(DestructRest::Drop) | None => {}132			}133134			{135				#[derive(Trace)]136				struct EndThunk {137					full: Thunk<ArrValue>,138					index: usize,139					end: usize,140				}141				impl ThunkValue for EndThunk {142					type Output = Val;143144					fn get(self: Box<Self>) -> Result<Self::Output> {145						let full = self.full.evaluate()?;146						Ok(full147							.get(full.len() - self.end + self.index)?148							.expect("length is checked"))149					}150				}151				for (i, d) in end.iter().enumerate() {152					destruct(153						d,154						Thunk::new(EndThunk {155							full: full.clone(),156							index: i,157							end: end.len(),158						}),159						fctx.clone(),160						new_bindings,161					)?;162				}163			}164		}165		#[cfg(feature = "exp-destruct")]166		Destruct::Object { fields, rest } => {167			use crate::obj::ObjValue;168169			#[derive(Trace)]170			struct DataThunk {171				parent: Thunk<Val>,172				field_names: Vec<IStr>,173				has_rest: bool,174			}175			impl ThunkValue for DataThunk {176				type Output = ObjValue;177178				fn get(self: Box<Self>) -> Result<Self::Output> {179					let v = self.parent.evaluate()?;180					let Val::Obj(obj) = v else {181						bail!("expected object");182					};183					for field in &self.field_names {184						if !obj.has_field_ex(field.clone(), true) {185							bail!("missing field: {field}");186						}187					}188					if !self.has_rest {189						let len = obj.len();190						if len != self.field_names.len() {191							bail!("too many fields, and rest not found");192						}193					}194					Ok(obj)195				}196			}197			let field_names: Vec<_> = fields198				.iter()199				.filter(|f| f.2.is_none())200				.map(|f| f.0.clone())201				.collect();202			let full = Thunk::new(DataThunk {203				parent,204				field_names,205				has_rest: rest.is_some(),206			});207208			for (field, d, default) in fields {209				#[derive(Trace)]210				struct FieldThunk {211					full: Thunk<ObjValue>,212					field: IStr,213					default: Option<(Pending<Context>, LocExpr)>,214				}215				impl ThunkValue for FieldThunk {216					type Output = Val;217218					fn get(self: Box<Self>) -> Result<Self::Output> {219						let full = self.full.evaluate()?;220						if let Some(field) = full.get(self.field)? {221							Ok(field)222						} else {223							let (fctx, expr) = self.default.as_ref().expect("shape is checked");224							Ok(evaluate(fctx.clone().unwrap(), expr)?)225						}226					}227				}228				let value = Thunk::new(FieldThunk {229					full: full.clone(),230					field: field.clone(),231					default: default.clone().map(|e| (fctx.clone(), e)),232				});233				if let Some(d) = d {234					destruct(d, value, fctx.clone(), new_bindings)?;235				} else {236					destruct(237						&Destruct::Full(field.clone()),238						value,239						fctx.clone(),240						new_bindings,241					)?;242				}243			}244		}245	}246	Ok(())247}248249pub fn evaluate_dest(250	d: &BindSpec,251	fctx: Pending<Context>,252	new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,253) -> Result<()> {254	match d {255		BindSpec::Field { into, value } => {256			#[derive(Trace)]257			struct EvaluateThunkValue {258				name: Option<IStr>,259				fctx: Pending<Context>,260				expr: LocExpr,261			}262			impl ThunkValue for EvaluateThunkValue {263				type Output = Val;264				fn get(self: Box<Self>) -> Result<Self::Output> {265					self.name.map_or_else(266						|| evaluate(self.fctx.unwrap(), &self.expr),267						|name| evaluate_named(self.fctx.unwrap(), &self.expr, name),268					)269				}270			}271			let data = Thunk::new(EvaluateThunkValue {272				name: into.name(),273				fctx: fctx.clone(),274				expr: value.clone(),275			});276			destruct(into, data, fctx, new_bindings)?;277		}278		BindSpec::Function {279			name,280			params,281			value,282		} => {283			#[derive(Trace)]284			struct MethodThunk {285				fctx: Pending<Context>,286				name: IStr,287				params: ParamsDesc,288				value: LocExpr,289			}290			impl ThunkValue for MethodThunk {291				type Output = Val;292293				fn get(self: Box<Self>) -> Result<Self::Output> {294					Ok(evaluate_method(295						self.fctx.unwrap(),296						self.name,297						self.params,298						self.value,299					))300				}301			}302303			let old = new_bindings.insert(304				name.clone(),305				Thunk::new(MethodThunk {306					fctx,307					name: name.clone(),308					params: params.clone(),309					value: value.clone(),310				}),311			);312			if old.is_some() {313				bail!(DuplicateLocalVar(name.clone()))314			}315		}316	}317	Ok(())318}