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

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, ObjValueBuilder,8	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(BindableMethodLazyVal {107					this,108					super_obj,109110					context_creator: self.context_creator.clone(),111					name: self.name.clone(),112					params: self.params.clone(),113					value: self.value.clone(),114				}))))115			}116		}117118		(119			b.name.clone(),120			LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableMethod {121				context_creator,122				name: b.name.clone(),123				params,124				value: b.value.clone(),125			})))),126		)127	} else {128		#[derive(Trace)]129		struct BindableNamedLazyVal {130			this: Option<ObjValue>,131			super_obj: Option<ObjValue>,132133			context_creator: ContextCreator,134			name: IStr,135			value: LocExpr,136		}137		impl LazyValValue for BindableNamedLazyVal {138			fn get(self: Box<Self>) -> Result<Val> {139				evaluate_named(140					self.context_creator.create(self.this, self.super_obj)?,141					&self.value,142					self.name,143				)144			}145		}146147		#[derive(Trace)]148		struct BindableNamed {149			context_creator: ContextCreator,150			name: IStr,151			value: LocExpr,152		}153		impl Bindable for BindableNamed {154			fn bind(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<LazyVal> {155				Ok(LazyVal::new(TraceBox(Box::new(BindableNamedLazyVal {156					this,157					super_obj,158159					context_creator: self.context_creator.clone(),160					name: self.name.clone(),161					value: self.value.clone(),162				}))))163			}164		}165166		(167			b.name.clone(),168			LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableNamed {169				context_creator,170				name: b.name.clone(),171				value: b.value.clone(),172			})))),173		)174	}175}176177pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {178	Val::Func(Cc::new(FuncVal::Normal(FuncDesc {179		name,180		ctx,181		params,182		body,183	})))184}185186pub fn evaluate_field_name(187	context: Context,188	field_name: &jrsonnet_parser::FieldName,189) -> Result<Option<IStr>> {190	Ok(match field_name {191		jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),192		jrsonnet_parser::FieldName::Dyn(expr) => {193			let value = evaluate(context, expr)?;194			if matches!(value, Val::Null) {195				None196			} else {197				Some(value.try_cast_str("dynamic field name")?)198			}199		}200	})201}202203pub fn evaluate_comp(204	context: Context,205	specs: &[CompSpec],206	callback: &mut impl FnMut(Context) -> Result<()>,207) -> Result<()> {208	match specs.get(0) {209		None => callback(context)?,210		Some(CompSpec::IfSpec(IfSpecData(cond))) => {211			if evaluate(context.clone(), cond)?.try_cast_bool("if spec")? {212				evaluate_comp(context, &specs[1..], callback)?213			}214		}215		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(context.clone(), expr)? {216			Val::Arr(list) => {217				for item in list.iter() {218					evaluate_comp(219						context.clone().with_var(var.clone(), item?.clone()),220						&specs[1..],221						callback,222					)?223				}224			}225			_ => throw!(InComprehensionCanOnlyIterateOverArray),226		},227	}228	Ok(())229}230231pub fn evaluate_member_list_object(context: Context, members: &[Member]) -> Result<ObjValue> {232	let new_bindings = FutureWrapper::new();233	let future_this = FutureWrapper::new();234	let context_creator = ContextCreator(context.clone(), new_bindings.clone());235	{236		let mut bindings: GcHashMap<IStr, LazyBinding> = GcHashMap::with_capacity(members.len());237		for (n, b) in members238			.iter()239			.filter_map(|m| match m {240				Member::BindStmt(b) => Some(b.clone()),241				_ => None,242			})243			.map(|b| evaluate_binding(&b, context_creator.clone()))244		{245			bindings.insert(n, b);246		}247		new_bindings.fill(bindings);248	}249250	let mut builder = ObjValueBuilder::new();251	for member in members.iter() {252		match member {253			Member::Field(FieldMember {254				name,255				plus,256				params: None,257				visibility,258				value,259			}) => {260				let name = evaluate_field_name(context.clone(), name)?;261				if name.is_none() {262					continue;263				}264				let name = name.unwrap();265266				#[derive(Trace)]267				struct ObjMemberBinding {268					context_creator: ContextCreator,269					value: LocExpr,270					name: IStr,271				}272				impl Bindable for ObjMemberBinding {273					fn bind(274						&self,275						this: Option<ObjValue>,276						super_obj: Option<ObjValue>,277					) -> Result<LazyVal> {278						Ok(LazyVal::new_resolved(evaluate_named(279							self.context_creator.create(this, super_obj)?,280							&self.value,281							self.name.clone(),282						)?))283					}284				}285				builder286					.member(name.clone())287					.with_add(*plus)288					.with_visibility(*visibility)289					.with_location(value.1.clone())290					.bindable(TraceBox(Box::new(ObjMemberBinding {291						context_creator: context_creator.clone(),292						value: value.clone(),293						name,294					})));295			}296			Member::Field(FieldMember {297				name,298				params: Some(params),299				value,300				..301			}) => {302				let name = evaluate_field_name(context.clone(), name)?;303				if name.is_none() {304					continue;305				}306				let name = name.unwrap();307				#[derive(Trace)]308				struct ObjMemberBinding {309					context_creator: ContextCreator,310					value: LocExpr,311					params: ParamsDesc,312					name: IStr,313				}314				impl Bindable for ObjMemberBinding {315					fn bind(316						&self,317						this: Option<ObjValue>,318						super_obj: Option<ObjValue>,319					) -> Result<LazyVal> {320						Ok(LazyVal::new_resolved(evaluate_method(321							self.context_creator.create(this, super_obj)?,322							self.name.clone(),323							self.params.clone(),324							self.value.clone(),325						)))326					}327				}328				builder329					.member(name.clone())330					.hide()331					.with_location(value.1.clone())332					.bindable(TraceBox(Box::new(ObjMemberBinding {333						context_creator: context_creator.clone(),334						value: value.clone(),335						params: params.clone(),336						name,337					})));338			}339			Member::BindStmt(_) => {}340			Member::AssertStmt(stmt) => {341				#[derive(Trace)]342				struct ObjectAssert {343					context_creator: ContextCreator,344					assert: AssertStmt,345				}346				impl ObjectAssertion for ObjectAssert {347					fn run(348						&self,349						this: Option<ObjValue>,350						super_obj: Option<ObjValue>,351					) -> Result<()> {352						let ctx = self.context_creator.create(this, super_obj)?;353						evaluate_assert(ctx, &self.assert)354					}355				}356				builder.assert(TraceBox(Box::new(ObjectAssert {357					context_creator: context_creator.clone(),358					assert: stmt.clone(),359				})));360			}361		}362	}363	let this = builder.build();364	future_this.fill(this.clone());365	Ok(this)366}367368pub fn evaluate_object(context: Context, object: &ObjBody) -> Result<ObjValue> {369	Ok(match object {370		ObjBody::MemberList(members) => evaluate_member_list_object(context, members)?,371		ObjBody::ObjComp(obj) => {372			let future_this = FutureWrapper::new();373			let mut builder = ObjValueBuilder::new();374			evaluate_comp(context.clone(), &obj.compspecs, &mut |ctx| {375				let new_bindings = FutureWrapper::new();376				let context_creator = ContextCreator(context.clone(), new_bindings.clone());377				let mut bindings: GcHashMap<IStr, LazyBinding> =378					GcHashMap::with_capacity(obj.pre_locals.len() + obj.post_locals.len());379				for (n, b) in obj380					.pre_locals381					.iter()382					.chain(obj.post_locals.iter())383					.map(|b| evaluate_binding(b, context_creator.clone()))384				{385					bindings.insert(n, b);386				}387				new_bindings.fill(bindings.clone());388				let ctx = ctx.extend_unbound(bindings, None, None, None)?;389				let key = evaluate(ctx.clone(), &obj.key)?;390391				match key {392					Val::Null => {}393					Val::Str(n) => {394						#[derive(Trace)]395						struct ObjCompBinding {396							context: Context,397							value: LocExpr,398						}399						impl Bindable for ObjCompBinding {400							fn bind(401								&self,402								this: Option<ObjValue>,403								_super_obj: Option<ObjValue>,404							) -> Result<LazyVal> {405								Ok(LazyVal::new_resolved(evaluate(406									self.context407										.clone()408										.extend(GcHashMap::new(), None, this, None),409									&self.value,410								)?))411							}412						}413						builder414							.member(n)415							.with_location(obj.value.1.clone())416							.with_add(obj.plus)417							.bindable(TraceBox(Box::new(ObjCompBinding {418								context: ctx,419								value: obj.value.clone(),420							})));421					}422					v => throw!(FieldMustBeStringGot(v.value_type())),423				}424425				Ok(())426			})?;427428			let this = builder.build();429			future_this.fill(this.clone());430			this431		}432	})433}434435pub fn evaluate_apply(436	context: Context,437	value: &LocExpr,438	args: &ArgsDesc,439	loc: &ExprLocation,440	tailstrict: bool,441) -> Result<Val> {442	let value = evaluate(context.clone(), value)?;443	Ok(match value {444		Val::Func(f) => {445			let body = || f.evaluate(context, loc, args, tailstrict);446			if tailstrict {447				body()?448			} else {449				push_frame(loc, || format!("function <{}> call", f.name()), body)?450			}451		}452		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),453	})454}455456pub fn evaluate_assert(context: Context, assertion: &AssertStmt) -> Result<()> {457	let value = &assertion.0;458	let msg = &assertion.1;459	let assertion_result = push_frame(460		&value.1,461		|| "assertion condition".to_owned(),462		|| {463			evaluate(context.clone(), value)?464				.try_cast_bool("assertion condition should be of type `boolean`")465		},466	)?;467	if !assertion_result {468		push_frame(469			&value.1,470			|| "assertion failure".to_owned(),471			|| {472				if let Some(msg) = msg {473					throw!(AssertionFailed(evaluate(context, msg)?.to_string()?));474				} else {475					throw!(AssertionFailed(Val::Null.to_string()?));476				}477			},478		)?479	}480	Ok(())481}482483pub fn evaluate_named(context: Context, lexpr: &LocExpr, name: IStr) -> Result<Val> {484	use Expr::*;485	let LocExpr(expr, _loc) = lexpr;486	Ok(match &**expr {487		Function(params, body) => evaluate_method(context, name, params.clone(), body.clone()),488		_ => evaluate(context, lexpr)?,489	})490}491492pub fn evaluate(context: Context, expr: &LocExpr) -> Result<Val> {493	use Expr::*;494	let LocExpr(expr, loc) = expr;495	// let bp = with_state(|s| s.0.stop_at.borrow().clone());496	Ok(match &**expr {497		Literal(LiteralType::This) => {498			Val::Obj(context.this().clone().ok_or(CantUseSelfOutsideOfObject)?)499		}500		Literal(LiteralType::Super) => Val::Obj(501			context502				.super_obj()503				.clone()504				.ok_or(NoSuperFound)?505				.with_this(context.this().clone().unwrap()),506		),507		Literal(LiteralType::Dollar) => {508			Val::Obj(context.dollar().clone().ok_or(NoTopLevelObjectFound)?)509		}510		Literal(LiteralType::True) => Val::Bool(true),511		Literal(LiteralType::False) => Val::Bool(false),512		Literal(LiteralType::Null) => Val::Null,513		Parened(e) => evaluate(context, e)?,514		Str(v) => Val::Str(v.clone()),515		Num(v) => Val::new_checked_num(*v)?,516		BinaryOp(v1, o, v2) => evaluate_binary_op_special(context, v1, *o, v2)?,517		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)?)?,518		Var(name) => push_frame(519			loc,520			|| format!("variable <{}> access", name),521			|| context.binding(name.clone())?.evaluate(),522		)?,523		Index(value, index) => {524			match (evaluate(context.clone(), value)?, evaluate(context, index)?) {525				(Val::Obj(v), Val::Str(s)) => {526					let sn = s.clone();527					push_frame(528						loc,529						|| format!("field <{}> access", sn),530						|| {531							if let Some(v) = v.get(s.clone())? {532								Ok(v)533							} else {534								throw!(NoSuchField(s))535							}536						},537					)?538				}539				(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(540					ValType::Obj,541					ValType::Str,542					n.value_type(),543				)),544545				(Val::Arr(v), Val::Num(n)) => {546					if n.fract() > f64::EPSILON {547						throw!(FractionalIndex)548					}549					v.get(n as usize)?550						.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?551				}552				(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),553				(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(554					ValType::Arr,555					ValType::Num,556					n.value_type(),557				)),558559				(Val::Str(s), Val::Num(n)) => Val::Str(560					s.chars()561						.skip(n as usize)562						.take(1)563						.collect::<String>()564						.into(),565				),566				(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(567					ValType::Str,568					ValType::Num,569					n.value_type(),570				)),571572				(v, _) => throw!(CantIndexInto(v.value_type())),573			}574		}575		LocalExpr(bindings, returned) => {576			let mut new_bindings: GcHashMap<IStr, LazyVal> =577				GcHashMap::with_capacity(bindings.len());578			let future_context = Context::new_future();579			for b in bindings {580				new_bindings.insert(581					b.name.clone(),582					evaluate_binding_in_future(b, future_context.clone()),583				);584			}585			let context = context586				.extend_bound(new_bindings)587				.into_future(future_context);588			evaluate(context, &returned.clone())?589		}590		Arr(items) => {591			let mut out = Vec::with_capacity(items.len());592			for item in items {593				// TODO: Implement ArrValue::Lazy with same context for every element?594				#[derive(Trace)]595				struct ArrayElement {596					context: Context,597					item: LocExpr,598				}599				impl LazyValValue for ArrayElement {600					fn get(self: Box<Self>) -> Result<Val> {601						evaluate(self.context, &self.item)602					}603				}604				out.push(LazyVal::new(TraceBox(Box::new(ArrayElement {605					context: context.clone(),606					item: item.clone(),607				}))));608			}609			Val::Arr(out.into())610		}611		ArrComp(expr, comp_specs) => {612			let mut out = Vec::new();613			evaluate_comp(context, comp_specs, &mut |ctx| {614				out.push(evaluate(ctx, expr)?);615				Ok(())616			})?;617			Val::Arr(ArrValue::Eager(Cc::new(out)))618		}619		Obj(body) => Val::Obj(evaluate_object(context, body)?),620		ObjExtend(s, t) => evaluate_add_op(621			&evaluate(context.clone(), s)?,622			&Val::Obj(evaluate_object(context, t)?),623		)?,624		Apply(value, args, tailstrict) => evaluate_apply(context, value, args, loc, *tailstrict)?,625		Function(params, body) => {626			evaluate_method(context, "anonymous".into(), params.clone(), body.clone())627		}628		Intrinsic(name) => Val::Func(Cc::new(FuncVal::Intrinsic(name.clone()))),629		AssertExpr(assert, returned) => {630			evaluate_assert(context.clone(), assert)?;631			evaluate(context, returned)?632		}633		ErrorStmt(e) => push_frame(634			loc,635			|| "error statement".to_owned(),636			|| {637				throw!(RuntimeError(638					evaluate(context, e)?.try_cast_str("error text should be of type `string`")?,639				))640			},641		)?,642		IfElse {643			cond,644			cond_then,645			cond_else,646		} => {647			if push_frame(648				loc,649				|| "if condition".to_owned(),650				|| evaluate(context.clone(), &cond.0)?.try_cast_bool("in if condition"),651			)? {652				evaluate(context, cond_then)?653			} else {654				match cond_else {655					Some(v) => evaluate(context, v)?,656					None => Val::Null,657				}658			}659		}660		Slice(value, desc) => {661			let indexable = evaluate(context.clone(), value)?;662663			fn parse_num(664				context: &Context,665				expr: Option<&LocExpr>,666				desc: &'static str,667			) -> Result<Option<usize>> {668				Ok(match expr {669					Some(s) => evaluate(context.clone(), s)?670						.try_cast_nullable_num(desc)?671						.map(|v| v as usize),672					None => None,673				})674			}675676			let start = parse_num(&context, desc.start.as_ref(), "start")?;677			let end = parse_num(&context, desc.end.as_ref(), "end")?;678			let step = parse_num(&context, desc.step.as_ref(), "step")?;679680			std_slice(indexable.into_indexable()?, start, end, step)?681		}682		Import(path) => {683			let tmp = loc.clone().0;684			let mut import_location = tmp.to_path_buf();685			import_location.pop();686			push_frame(687				loc,688				|| format!("import {:?}", path),689				|| with_state(|s| s.import_file(&import_location, path)),690			)?691		}692		ImportStr(path) => {693			let tmp = loc.clone().0;694			let mut import_location = tmp.to_path_buf();695			import_location.pop();696			Val::Str(with_state(|s| s.import_file_str(&import_location, path))?)697		}698	})699}