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

difftreelog

source

crates/jrsonnet-evaluator/src/evaluate/mod.rs17.6 KiBsourcehistory
1use crate::{2	builtin::std_slice,3	error::Error::*,4	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},5	gc::TraceBox,6	push_frame, throw, with_state, ArrValue, Bindable, Context, ContextCreator, FuncDesc, FuncVal,7	FutureWrapper, GcHashMap, LazyBinding, LazyVal, LazyValValue, ObjValue,8	ObjValueBuilder, ObjectAssertion, Result, Val,9};10use gcmodule::{Cc, Trace};11use jrsonnet_interner::IStr;12use jrsonnet_parser::{13	ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, ExprLocation, FieldMember, ForSpecData,14	IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc,15};16use jrsonnet_types::ValType;17pub mod operator;1819pub fn evaluate_binding_in_future(20	b: &BindSpec,21	context_creator: FutureWrapper<Context>,22) -> LazyVal {23	let b = b.clone();24	if let Some(params) = &b.params {25		let params = params.clone();2627		#[derive(Trace)]28		struct LazyMethodBinding {29			context_creator: FutureWrapper<Context>,30			name: IStr,31			params: ParamsDesc,32			value: LocExpr,33		}34		impl LazyValValue for LazyMethodBinding {35			fn get(self: Box<Self>) -> Result<Val> {36				Ok(evaluate_method(37					self.context_creator.unwrap(),38					self.name,39					self.params,40					self.value,41				))42			}43		}4445		LazyVal::new(TraceBox(Box::new(LazyMethodBinding {46			context_creator,47			name: b.name.clone(),48			params,49			value: b.value.clone(),50		})))51	} else {52		#[derive(Trace)]53		struct LazyNamedBinding {54			context_creator: FutureWrapper<Context>,55			name: IStr,56			value: LocExpr,57		}58		impl LazyValValue for LazyNamedBinding {59			fn get(self: Box<Self>) -> Result<Val> {60				evaluate_named(self.context_creator.unwrap(), &self.value, self.name)61			}62		}63		LazyVal::new(TraceBox(Box::new(LazyNamedBinding {64			context_creator,65			name: b.name.clone(),66			value: b.value,67		})))68	}69}7071pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (IStr, LazyBinding) {72	let b = b.clone();73	if let Some(params) = &b.params {74		let params = params.clone();7576		#[derive(Trace)]77		struct BindableMethodLazyVal {78			this: Option<ObjValue>,79			super_obj: Option<ObjValue>,8081			context_creator: ContextCreator,82			name: IStr,83			params: ParamsDesc,84			value: LocExpr,85		}86		impl LazyValValue for BindableMethodLazyVal {87			fn get(self: Box<Self>) -> Result<Val> {88				Ok(evaluate_method(89					self.context_creator.create(self.this, self.super_obj)?,90					self.name,91					self.params,92					self.value,93				))94			}95		}9697		#[derive(Trace)]98		struct BindableMethod {99			context_creator: ContextCreator,100			name: IStr,101			params: ParamsDesc,102			value: LocExpr,103		}104		impl Bindable for BindableMethod {105			fn bind(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<LazyVal> {106				Ok(LazyVal::new(TraceBox(Box::new(107					BindableMethodLazyVal {108						this,109						super_obj,110111						context_creator: self.context_creator.clone(),112						name: self.name.clone(),113						params: self.params.clone(),114						value: self.value.clone(),115					},116				))))117			}118		}119120		(121			b.name.clone(),122			LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableMethod {123				context_creator,124				name: b.name.clone(),125				params,126				value: b.value.clone(),127			})))),128		)129	} else {130		#[derive(Trace)]131		struct BindableNamedLazyVal {132			this: Option<ObjValue>,133			super_obj: Option<ObjValue>,134135			context_creator: ContextCreator,136			name: IStr,137			value: LocExpr,138		}139		impl LazyValValue for BindableNamedLazyVal {140			fn get(self: Box<Self>) -> Result<Val> {141				evaluate_named(142					self.context_creator.create(self.this, self.super_obj)?,143					&self.value,144					self.name,145				)146			}147		}148149		#[derive(Trace)]150		struct BindableNamed {151			context_creator: ContextCreator,152			name: IStr,153			value: LocExpr,154		}155		impl Bindable for BindableNamed {156			fn bind(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<LazyVal> {157				Ok(LazyVal::new(TraceBox(Box::new(158					BindableNamedLazyVal {159						this,160						super_obj,161162						context_creator: self.context_creator.clone(),163						name: self.name.clone(),164						value: self.value.clone(),165					},166				))))167			}168		}169170		(171			b.name.clone(),172			LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableNamed {173				context_creator,174				name: b.name.clone(),175				value: b.value.clone(),176			})))),177		)178	}179}180181pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {182	Val::Func(Cc::new(FuncVal::Normal(FuncDesc {183		name,184		ctx,185		params,186		body,187	})))188}189190pub fn evaluate_field_name(191	context: Context,192	field_name: &jrsonnet_parser::FieldName,193) -> Result<Option<IStr>> {194	Ok(match field_name {195		jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),196		jrsonnet_parser::FieldName::Dyn(expr) => {197			let value = evaluate(context, expr)?;198			if matches!(value, Val::Null) {199				None200			} else {201				Some(value.try_cast_str("dynamic field name")?)202			}203		}204	})205}206207pub fn evaluate_comp(208	context: Context,209	specs: &[CompSpec],210	callback: &mut impl FnMut(Context) -> Result<()>,211) -> Result<()> {212	match specs.get(0) {213		None => callback(context)?,214		Some(CompSpec::IfSpec(IfSpecData(cond))) => {215			if evaluate(context.clone(), cond)?.try_cast_bool("if spec")? {216				evaluate_comp(context, &specs[1..], callback)?217			}218		}219		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(context.clone(), expr)? {220			Val::Arr(list) => {221				for item in list.iter() {222					evaluate_comp(223						context.clone().with_var(var.clone(), item?.clone()),224						&specs[1..],225						callback,226					)?227				}228			}229			_ => throw!(InComprehensionCanOnlyIterateOverArray),230		},231	}232	Ok(())233}234235pub fn evaluate_member_list_object(context: Context, members: &[Member]) -> Result<ObjValue> {236	let new_bindings = FutureWrapper::new();237	let future_this = FutureWrapper::new();238	let context_creator = ContextCreator(context.clone(), new_bindings.clone());239	{240		let mut bindings: GcHashMap<IStr, LazyBinding> = GcHashMap::with_capacity(members.len());241		for (n, b) in members242			.iter()243			.filter_map(|m| match m {244				Member::BindStmt(b) => Some(b.clone()),245				_ => None,246			})247			.map(|b| evaluate_binding(&b, context_creator.clone()))248		{249			bindings.insert(n, b);250		}251		new_bindings.fill(bindings);252	}253254	let mut builder = ObjValueBuilder::new();255	for member in members.iter() {256		match member {257			Member::Field(FieldMember {258				name,259				plus,260				params: None,261				visibility,262				value,263			}) => {264				let name = evaluate_field_name(context.clone(), name)?;265				if name.is_none() {266					continue;267				}268				let name = name.unwrap();269270				#[derive(Trace)]271				struct ObjMemberBinding {272					context_creator: ContextCreator,273					value: LocExpr,274					name: IStr,275				}276				impl Bindable for ObjMemberBinding {277					fn bind(278						&self,279						this: Option<ObjValue>,280						super_obj: Option<ObjValue>,281					) -> Result<LazyVal> {282						Ok(LazyVal::new_resolved(evaluate_named(283							self.context_creator.create(this, super_obj)?,284							&self.value,285							self.name.clone(),286						)?))287					}288				}289				builder290					.member(name.clone())291					.with_add(*plus)292					.with_visibility(*visibility)293					.with_location(value.1.clone())294					.bindable(TraceBox(Box::new(ObjMemberBinding {295						context_creator: context_creator.clone(),296						value: value.clone(),297						name,298					})));299			}300			Member::Field(FieldMember {301				name,302				params: Some(params),303				value,304				..305			}) => {306				let name = evaluate_field_name(context.clone(), name)?;307				if name.is_none() {308					continue;309				}310				let name = name.unwrap();311				#[derive(Trace)]312				struct ObjMemberBinding {313					context_creator: ContextCreator,314					value: LocExpr,315					params: ParamsDesc,316					name: IStr,317				}318				impl Bindable for ObjMemberBinding {319					fn bind(320						&self,321						this: Option<ObjValue>,322						super_obj: Option<ObjValue>,323					) -> Result<LazyVal> {324						Ok(LazyVal::new_resolved(evaluate_method(325							self.context_creator.create(this, super_obj)?,326							self.name.clone(),327							self.params.clone(),328							self.value.clone(),329						)))330					}331				}332				builder333					.member(name.clone())334					.hide()335					.with_location(value.1.clone())336					.bindable(TraceBox(Box::new(ObjMemberBinding {337						context_creator: context_creator.clone(),338						value: value.clone(),339						params: params.clone(),340						name,341					})));342			}343			Member::BindStmt(_) => {}344			Member::AssertStmt(stmt) => {345				#[derive(Trace)]346				struct ObjectAssert {347					context_creator: ContextCreator,348					assert: AssertStmt,349				}350				impl ObjectAssertion for ObjectAssert {351					fn run(352						&self,353						this: Option<ObjValue>,354						super_obj: Option<ObjValue>,355					) -> Result<()> {356						let ctx = self.context_creator.create(this, super_obj)?;357						evaluate_assert(ctx, &self.assert)358					}359				}360				builder.assert(TraceBox(Box::new(ObjectAssert {361					context_creator: context_creator.clone(),362					assert: stmt.clone(),363				})));364			}365		}366	}367	let this = builder.build();368	future_this.fill(this.clone());369	Ok(this)370}371372pub fn evaluate_object(context: Context, object: &ObjBody) -> Result<ObjValue> {373	Ok(match object {374		ObjBody::MemberList(members) => evaluate_member_list_object(context, members)?,375		ObjBody::ObjComp(obj) => {376			let future_this = FutureWrapper::new();377			let mut builder = ObjValueBuilder::new();378			evaluate_comp(context.clone(), &obj.compspecs, &mut |ctx| {379				let new_bindings = FutureWrapper::new();380				let context_creator = ContextCreator(context.clone(), new_bindings.clone());381				let mut bindings: GcHashMap<IStr, LazyBinding> =382					GcHashMap::with_capacity(obj.pre_locals.len() + obj.post_locals.len());383				for (n, b) in obj384					.pre_locals385					.iter()386					.chain(obj.post_locals.iter())387					.map(|b| evaluate_binding(b, context_creator.clone()))388				{389					bindings.insert(n, b);390				}391				new_bindings.fill(bindings.clone());392				let ctx = ctx.extend_unbound(bindings, None, None, None)?;393				let key = evaluate(ctx.clone(), &obj.key)?;394395				match key {396					Val::Null => {}397					Val::Str(n) => {398						#[derive(Trace)]399						struct ObjCompBinding {400							context: Context,401							value: LocExpr,402						}403						impl Bindable for ObjCompBinding {404							fn bind(405								&self,406								this: Option<ObjValue>,407								_super_obj: Option<ObjValue>,408							) -> Result<LazyVal> {409								Ok(LazyVal::new_resolved(evaluate(410									self.context411										.clone()412										.extend(GcHashMap::new(), None, this, None),413									&self.value,414								)?))415							}416						}417						builder418							.member(n)419							.with_location(obj.value.1.clone())420							.with_add(obj.plus)421							.bindable(TraceBox(Box::new(ObjCompBinding {422								context: ctx,423								value: obj.value.clone(),424							})));425					}426					v => throw!(FieldMustBeStringGot(v.value_type())),427				}428429				Ok(())430			})?;431432			let this = builder.build();433			future_this.fill(this.clone());434			this435		}436	})437}438439pub fn evaluate_apply(440	context: Context,441	value: &LocExpr,442	args: &ArgsDesc,443	loc: &ExprLocation,444	tailstrict: bool,445) -> Result<Val> {446	let value = evaluate(context.clone(), value)?;447	Ok(match value {448		Val::Func(f) => {449			let body = || f.evaluate(context, loc, args, tailstrict);450			if tailstrict {451				body()?452			} else {453				push_frame(loc, || format!("function <{}> call", f.name()), body)?454			}455		}456		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),457	})458}459460pub fn evaluate_assert(context: Context, assertion: &AssertStmt) -> Result<()> {461	let value = &assertion.0;462	let msg = &assertion.1;463	let assertion_result = push_frame(464		&value.1,465		|| "assertion condition".to_owned(),466		|| {467			evaluate(context.clone(), value)?468				.try_cast_bool("assertion condition should be of type `boolean`")469		},470	)?;471	if !assertion_result {472		push_frame(473			&value.1,474			|| "assertion failure".to_owned(),475			|| {476				if let Some(msg) = msg {477					throw!(AssertionFailed(evaluate(context, msg)?.to_string()?));478				} else {479					throw!(AssertionFailed(Val::Null.to_string()?));480				}481			},482		)?483	}484	Ok(())485}486487pub fn evaluate_named(context: Context, lexpr: &LocExpr, name: IStr) -> Result<Val> {488	use Expr::*;489	let LocExpr(expr, _loc) = lexpr;490	Ok(match &**expr {491		Function(params, body) => evaluate_method(context, name, params.clone(), body.clone()),492		_ => evaluate(context, lexpr)?,493	})494}495496pub fn evaluate(context: Context, expr: &LocExpr) -> Result<Val> {497	use Expr::*;498	let LocExpr(expr, loc) = expr;499	// let bp = with_state(|s| s.0.stop_at.borrow().clone());500	Ok(match &**expr {501		Literal(LiteralType::This) => {502			Val::Obj(context.this().clone().ok_or(CantUseSelfOutsideOfObject)?)503		}504		Literal(LiteralType::Super) => Val::Obj(505			context506				.super_obj()507				.clone()508				.ok_or(NoSuperFound)?509				.with_this(context.this().clone().unwrap()),510		),511		Literal(LiteralType::Dollar) => {512			Val::Obj(context.dollar().clone().ok_or(NoTopLevelObjectFound)?)513		}514		Literal(LiteralType::True) => Val::Bool(true),515		Literal(LiteralType::False) => Val::Bool(false),516		Literal(LiteralType::Null) => Val::Null,517		Parened(e) => evaluate(context, e)?,518		Str(v) => Val::Str(v.clone()),519		Num(v) => Val::new_checked_num(*v)?,520		BinaryOp(v1, o, v2) => evaluate_binary_op_special(context, v1, *o, v2)?,521		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)?)?,522		Var(name) => push_frame(523			loc,524			|| format!("variable <{}> access", name),525			|| context.binding(name.clone())?.evaluate(),526		)?,527		Index(value, index) => {528			match (evaluate(context.clone(), value)?, evaluate(context, index)?) {529				(Val::Obj(v), Val::Str(s)) => {530					let sn = s.clone();531					push_frame(532						loc,533						|| format!("field <{}> access", sn),534						|| {535							if let Some(v) = v.get(s.clone())? {536								Ok(v)537							} else {538								throw!(NoSuchField(s))539							}540						},541					)?542				}543				(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(544					ValType::Obj,545					ValType::Str,546					n.value_type(),547				)),548549				(Val::Arr(v), Val::Num(n)) => {550					if n.fract() > f64::EPSILON {551						throw!(FractionalIndex)552					}553					v.get(n as usize)?554						.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?555				}556				(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),557				(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(558					ValType::Arr,559					ValType::Num,560					n.value_type(),561				)),562563				(Val::Str(s), Val::Num(n)) => Val::Str(564					s.chars()565						.skip(n as usize)566						.take(1)567						.collect::<String>()568						.into(),569				),570				(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(571					ValType::Str,572					ValType::Num,573					n.value_type(),574				)),575576				(v, _) => throw!(CantIndexInto(v.value_type())),577			}578		}579		LocalExpr(bindings, returned) => {580			let mut new_bindings: GcHashMap<IStr, LazyVal> =581				GcHashMap::with_capacity(bindings.len());582			let future_context = Context::new_future();583			for b in bindings {584				new_bindings.insert(585					b.name.clone(),586					evaluate_binding_in_future(b, future_context.clone()),587				);588			}589			let context = context590				.extend_bound(new_bindings)591				.into_future(future_context);592			evaluate(context, &returned.clone())?593		}594		Arr(items) => {595			let mut out = Vec::with_capacity(items.len());596			for item in items {597				// TODO: Implement ArrValue::Lazy with same context for every element?598				#[derive(Trace)]599				struct ArrayElement {600					context: Context,601					item: LocExpr,602				}603				impl LazyValValue for ArrayElement {604					fn get(self: Box<Self>) -> Result<Val> {605						evaluate(self.context, &self.item)606					}607				}608				out.push(LazyVal::new(TraceBox(Box::new(ArrayElement {609					context: context.clone(),610					item: item.clone(),611				}))));612			}613			Val::Arr(out.into())614		}615		ArrComp(expr, comp_specs) => {616			let mut out = Vec::new();617			evaluate_comp(context, comp_specs, &mut |ctx| {618				out.push(evaluate(ctx, expr)?);619				Ok(())620			})?;621			Val::Arr(ArrValue::Eager(Cc::new(out)))622		}623		Obj(body) => Val::Obj(evaluate_object(context, body)?),624		ObjExtend(s, t) => evaluate_add_op(625			&evaluate(context.clone(), s)?,626			&Val::Obj(evaluate_object(context, t)?),627		)?,628		Apply(value, args, tailstrict) => evaluate_apply(context, value, args, loc, *tailstrict)?,629		Function(params, body) => {630			evaluate_method(context, "anonymous".into(), params.clone(), body.clone())631		}632		Intrinsic(name) => Val::Func(Cc::new(FuncVal::Intrinsic(name.clone()))),633		AssertExpr(assert, returned) => {634			evaluate_assert(context.clone(), assert)?;635			evaluate(context, returned)?636		}637		ErrorStmt(e) => push_frame(638			loc,639			|| "error statement".to_owned(),640			|| {641				throw!(RuntimeError(642					evaluate(context, e)?.try_cast_str("error text should be of type `string`")?,643				))644			},645		)?,646		IfElse {647			cond,648			cond_then,649			cond_else,650		} => {651			if push_frame(652				loc,653				|| "if condition".to_owned(),654				|| evaluate(context.clone(), &cond.0)?.try_cast_bool("in if condition"),655			)? {656				evaluate(context, cond_then)?657			} else {658				match cond_else {659					Some(v) => evaluate(context, v)?,660					None => Val::Null,661				}662			}663		}664		Slice(value, desc) => {665			let indexable = evaluate(context.clone(), value)?;666667			fn parse_num(668				context: &Context,669				expr: Option<&LocExpr>,670				desc: &'static str,671			) -> Result<Option<usize>> {672				Ok(match expr {673					Some(s) => evaluate(context.clone(), s)?674						.try_cast_nullable_num(desc)?675						.map(|v| v as usize),676					None => None,677				})678			}679680			let start = parse_num(&context, desc.start.as_ref(), "start")?;681			let end = parse_num(&context, desc.end.as_ref(), "end")?;682			let step = parse_num(&context, desc.step.as_ref(), "step")?;683684			std_slice(indexable.into_indexable()?, start, end, step)?685		}686		Import(path) => {687			let tmp = loc.clone().0;688			let mut import_location = tmp.to_path_buf();689			import_location.pop();690			push_frame(691				loc,692				|| format!("import {:?}", path),693				|| with_state(|s| s.import_file(&import_location, path)),694			)?695		}696		ImportStr(path) => {697			let tmp = loc.clone().0;698			let mut import_location = tmp.to_path_buf();699			import_location.pop();700			Val::Str(with_state(|s| s.import_file_str(&import_location, path))?)701		}702	})703}