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

difftreelog

source

crates/jrsonnet-evaluator/src/evaluate/mod.rs18.8 KiBsourcehistory
1use gcmodule::{Cc, Trace};2use jrsonnet_interner::IStr;3use jrsonnet_parser::{4	ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, FieldMember, ForSpecData, IfSpecData,5	LiteralType, LocExpr, Member, ObjBody, ParamsDesc,6};7use jrsonnet_types::ValType;89use crate::{10	error::Error::*,11	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},12	function::{CallLocation, FuncDesc, FuncVal},13	gc::TraceBox,14	stdlib::{std_slice, BUILTINS},15	throw,16	typed::Typed,17	val::{ArrValue, LazyValValue},18	Bindable, Context, ContextCreator, FutureWrapper, GcHashMap, LazyBinding, LazyVal, ObjValue,19	ObjValueBuilder, ObjectAssertion, Result, State, Val,20};21pub mod operator;2223pub fn evaluate_binding_in_future(b: &BindSpec, fctx: FutureWrapper<Context>) -> LazyVal {24	let b = b.clone();25	if let Some(params) = &b.params {26		#[derive(Trace)]27		struct LazyMethodBinding {28			fctx: FutureWrapper<Context>,29			name: IStr,30			params: ParamsDesc,31			value: LocExpr,32		}33		impl LazyValValue for LazyMethodBinding {34			fn get(self: Box<Self>, _: State) -> Result<Val> {35				Ok(evaluate_method(36					self.fctx.unwrap(),37					self.name,38					self.params,39					self.value,40				))41			}42		}4344		let params = params.clone();4546		LazyVal::new(TraceBox(Box::new(LazyMethodBinding {47			fctx,48			name: b.name.clone(),49			params,50			value: b.value.clone(),51		})))52	} else {53		#[derive(Trace)]54		struct LazyNamedBinding {55			fctx: FutureWrapper<Context>,56			name: IStr,57			value: LocExpr,58		}59		impl LazyValValue for LazyNamedBinding {60			fn get(self: Box<Self>, s: State) -> Result<Val> {61				evaluate_named(s, self.fctx.unwrap(), &self.value, self.name)62			}63		}64		LazyVal::new(TraceBox(Box::new(LazyNamedBinding {65			fctx,66			name: b.name.clone(),67			value: b.value,68		})))69	}70}7172#[allow(clippy::too_many_lines)]73pub fn evaluate_binding(b: &BindSpec, cctx: ContextCreator) -> (IStr, LazyBinding) {74	let b = b.clone();75	if let Some(params) = &b.params {76		#[derive(Trace)]77		struct BindableMethodLazyVal {78			this: Option<ObjValue>,79			super_obj: Option<ObjValue>,8081			cctx: ContextCreator,82			name: IStr,83			params: ParamsDesc,84			value: LocExpr,85		}86		impl LazyValValue for BindableMethodLazyVal {87			fn get(self: Box<Self>, s: State) -> Result<Val> {88				Ok(evaluate_method(89					self.cctx.create(s, self.this, self.super_obj)?,90					self.name,91					self.params,92					self.value,93				))94			}95		}9697		#[derive(Trace)]98		struct BindableMethod {99			cctx: ContextCreator,100			name: IStr,101			params: ParamsDesc,102			value: LocExpr,103		}104		impl Bindable for BindableMethod {105			fn bind(106				&self,107				_: State,108				this: Option<ObjValue>,109				super_obj: Option<ObjValue>,110			) -> Result<LazyVal> {111				Ok(LazyVal::new(TraceBox(Box::new(BindableMethodLazyVal {112					this,113					super_obj,114115					cctx: self.cctx.clone(),116					name: self.name.clone(),117					params: self.params.clone(),118					value: self.value.clone(),119				}))))120			}121		}122123		let params = params.clone();124125		(126			b.name.clone(),127			LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableMethod {128				cctx,129				name: b.name.clone(),130				params,131				value: b.value.clone(),132			})))),133		)134	} else {135		#[derive(Trace)]136		struct BindableNamedLazyVal {137			this: Option<ObjValue>,138			super_obj: Option<ObjValue>,139140			cctx: ContextCreator,141			name: IStr,142			value: LocExpr,143		}144		impl LazyValValue for BindableNamedLazyVal {145			fn get(self: Box<Self>, s: State) -> Result<Val> {146				evaluate_named(147					s.clone(),148					self.cctx.create(s, self.this, self.super_obj)?,149					&self.value,150					self.name,151				)152			}153		}154155		#[derive(Trace)]156		struct BindableNamed {157			cctx: ContextCreator,158			name: IStr,159			value: LocExpr,160		}161		impl Bindable for BindableNamed {162			fn bind(163				&self,164				_: State,165				this: Option<ObjValue>,166				super_obj: Option<ObjValue>,167			) -> Result<LazyVal> {168				Ok(LazyVal::new(TraceBox(Box::new(BindableNamedLazyVal {169					this,170					super_obj,171172					cctx: self.cctx.clone(),173					name: self.name.clone(),174					value: self.value.clone(),175				}))))176			}177		}178179		(180			b.name.clone(),181			LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableNamed {182				cctx,183				name: b.name.clone(),184				value: b.value.clone(),185			})))),186		)187	}188}189190pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {191	Val::Func(FuncVal::Normal(Cc::new(FuncDesc {192		name,193		ctx,194		params,195		body,196	})))197}198199pub fn evaluate_field_name(200	s: State,201	ctx: Context,202	field_name: &jrsonnet_parser::FieldName,203) -> Result<Option<IStr>> {204	Ok(match field_name {205		jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),206		jrsonnet_parser::FieldName::Dyn(expr) => s.push(207			CallLocation::new(&expr.1),208			|| "evaluating field name".to_string(),209			|| {210				let value = evaluate(s.clone(), ctx, expr)?;211				if matches!(value, Val::Null) {212					Ok(None)213				} else {214					Ok(Some(IStr::from_untyped(value, s.clone())?))215				}216			},217		)?,218	})219}220221pub fn evaluate_comp(222	s: State,223	ctx: Context,224	specs: &[CompSpec],225	callback: &mut impl FnMut(Context) -> Result<()>,226) -> Result<()> {227	match specs.get(0) {228		None => callback(ctx)?,229		Some(CompSpec::IfSpec(IfSpecData(cond))) => {230			if bool::from_untyped(evaluate(s.clone(), ctx.clone(), cond)?, s.clone())? {231				evaluate_comp(s, ctx, &specs[1..], callback)?;232			}233		}234		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {235			match evaluate(s.clone(), ctx.clone(), expr)? {236				Val::Arr(list) => {237					for item in list.iter(s.clone()) {238						evaluate_comp(239							s.clone(),240							ctx.clone().with_var(var.clone(), item?.clone()),241							&specs[1..],242							callback,243						)?;244					}245				}246				_ => throw!(InComprehensionCanOnlyIterateOverArray),247			}248		}249	}250	Ok(())251}252253#[allow(clippy::too_many_lines)]254pub fn evaluate_member_list_object(s: State, ctx: Context, members: &[Member]) -> Result<ObjValue> {255	let new_bindings = FutureWrapper::new();256	let future_this = FutureWrapper::new();257	let cctx = ContextCreator(ctx.clone(), new_bindings.clone());258	{259		let mut bindings: GcHashMap<IStr, LazyBinding> = GcHashMap::with_capacity(members.len());260		for (n, b) in members261			.iter()262			.filter_map(|m| match m {263				Member::BindStmt(b) => Some(b.clone()),264				_ => None,265			})266			.map(|b| evaluate_binding(&b, cctx.clone()))267		{268			bindings.insert(n, b);269		}270		new_bindings.fill(bindings);271	}272273	let mut builder = ObjValueBuilder::new();274	for member in members.iter() {275		match member {276			Member::Field(FieldMember {277				name,278				plus,279				params: None,280				visibility,281				value,282			}) => {283				#[derive(Trace)]284				struct ObjMemberBinding {285					cctx: ContextCreator,286					value: LocExpr,287					name: IStr,288				}289				impl Bindable for ObjMemberBinding {290					fn bind(291						&self,292						s: State,293						this: Option<ObjValue>,294						super_obj: Option<ObjValue>,295					) -> Result<LazyVal> {296						Ok(LazyVal::new_resolved(evaluate_named(297							s.clone(),298							self.cctx.create(s, this, super_obj)?,299							&self.value,300							self.name.clone(),301						)?))302					}303				}304305				let name = evaluate_field_name(s.clone(), ctx.clone(), name)?;306				let name = if let Some(name) = name {307					name308				} else {309					continue;310				};311312				builder313					.member(name.clone())314					.with_add(*plus)315					.with_visibility(*visibility)316					.with_location(value.1.clone())317					.bindable(318						s.clone(),319						TraceBox(Box::new(ObjMemberBinding {320							cctx: cctx.clone(),321							value: value.clone(),322							name,323						})),324					)?;325			}326			Member::Field(FieldMember {327				name,328				params: Some(params),329				value,330				..331			}) => {332				#[derive(Trace)]333				struct ObjMemberBinding {334					cctx: ContextCreator,335					value: LocExpr,336					params: ParamsDesc,337					name: IStr,338				}339				impl Bindable for ObjMemberBinding {340					fn bind(341						&self,342						s: State,343						this: Option<ObjValue>,344						super_obj: Option<ObjValue>,345					) -> Result<LazyVal> {346						Ok(LazyVal::new_resolved(evaluate_method(347							self.cctx.create(s, this, super_obj)?,348							self.name.clone(),349							self.params.clone(),350							self.value.clone(),351						)))352					}353				}354355				let name = if let Some(name) = evaluate_field_name(s.clone(), ctx.clone(), name)? {356					name357				} else {358					continue;359				};360361				builder362					.member(name.clone())363					.hide()364					.with_location(value.1.clone())365					.bindable(366						s.clone(),367						TraceBox(Box::new(ObjMemberBinding {368							cctx: cctx.clone(),369							value: value.clone(),370							params: params.clone(),371							name,372						})),373					)?;374			}375			Member::BindStmt(_) => {}376			Member::AssertStmt(stmt) => {377				#[derive(Trace)]378				struct ObjectAssert {379					cctx: ContextCreator,380					assert: AssertStmt,381				}382				impl ObjectAssertion for ObjectAssert {383					fn run(384						&self,385						s: State,386						this: Option<ObjValue>,387						super_obj: Option<ObjValue>,388					) -> Result<()> {389						let ctx = self.cctx.create(s.clone(), this, super_obj)?;390						evaluate_assert(s, ctx, &self.assert)391					}392				}393				builder.assert(TraceBox(Box::new(ObjectAssert {394					cctx: cctx.clone(),395					assert: stmt.clone(),396				})));397			}398		}399	}400	let this = builder.build();401	future_this.fill(this.clone());402	Ok(this)403}404405pub fn evaluate_object(s: State, ctx: Context, object: &ObjBody) -> Result<ObjValue> {406	Ok(match object {407		ObjBody::MemberList(members) => evaluate_member_list_object(s, ctx, members)?,408		ObjBody::ObjComp(obj) => {409			let future_this = FutureWrapper::new();410			let mut builder = ObjValueBuilder::new();411			evaluate_comp(s.clone(), ctx, &obj.compspecs, &mut |ctx| {412				let new_bindings = FutureWrapper::new();413				let cctx = ContextCreator(ctx.clone(), new_bindings.clone());414				let mut bindings: GcHashMap<IStr, LazyBinding> =415					GcHashMap::with_capacity(obj.pre_locals.len() + obj.post_locals.len());416				for (n, b) in obj417					.pre_locals418					.iter()419					.chain(obj.post_locals.iter())420					.map(|b| evaluate_binding(b, cctx.clone()))421				{422					bindings.insert(n, b);423				}424				new_bindings.fill(bindings.clone());425				let ctx = ctx.extend_unbound(s.clone(), bindings, None, None, None)?;426				let key = evaluate(s.clone(), ctx.clone(), &obj.key)?;427428				match key {429					Val::Null => {}430					Val::Str(n) => {431						#[derive(Trace)]432						struct ObjCompBinding {433							ctx: Context,434							value: LocExpr,435						}436						impl Bindable for ObjCompBinding {437							fn bind(438								&self,439								s: State,440								this: Option<ObjValue>,441								_super_obj: Option<ObjValue>,442							) -> Result<LazyVal> {443								Ok(LazyVal::new_resolved(evaluate(444									s,445									self.ctx.clone().extend(GcHashMap::new(), None, this, None),446									&self.value,447								)?))448							}449						}450						builder451							.member(n)452							.with_location(obj.value.1.clone())453							.with_add(obj.plus)454							.bindable(455								s.clone(),456								TraceBox(Box::new(ObjCompBinding {457									ctx,458									value: obj.value.clone(),459								})),460							)?;461					}462					v => throw!(FieldMustBeStringGot(v.value_type())),463				}464465				Ok(())466			})?;467468			let this = builder.build();469			future_this.fill(this.clone());470			this471		}472	})473}474475pub fn evaluate_apply(476	s: State,477	ctx: Context,478	value: &LocExpr,479	args: &ArgsDesc,480	loc: CallLocation,481	tailstrict: bool,482) -> Result<Val> {483	let value = evaluate(s.clone(), ctx.clone(), value)?;484	Ok(match value {485		Val::Func(f) => {486			let body = || f.evaluate(s.clone(), ctx, loc, args, tailstrict);487			if tailstrict {488				body()?489			} else {490				s.push(loc, || format!("function <{}> call", f.name()), body)?491			}492		}493		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),494	})495}496497pub fn evaluate_assert(s: State, ctx: Context, assertion: &AssertStmt) -> Result<()> {498	let value = &assertion.0;499	let msg = &assertion.1;500	let assertion_result = s.push(501		CallLocation::new(&value.1),502		|| "assertion condition".to_owned(),503		|| bool::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),504	)?;505	if !assertion_result {506		s.push(507			CallLocation::new(&value.1),508			|| "assertion failure".to_owned(),509			|| {510				if let Some(msg) = msg {511					throw!(AssertionFailed(512						evaluate(s.clone(), ctx, msg)?.to_string(s.clone())?513					));514				}515				throw!(AssertionFailed(Val::Null.to_string(s.clone())?));516			},517		)?;518	}519	Ok(())520}521522pub fn evaluate_named(s: State, ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {523	use Expr::*;524	let LocExpr(raw_expr, _loc) = expr;525	Ok(match &**raw_expr {526		Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),527		_ => evaluate(s, ctx, expr)?,528	})529}530531#[allow(clippy::too_many_lines)]532pub fn evaluate(s: State, ctx: Context, expr: &LocExpr) -> Result<Val> {533	use Expr::*;534	let LocExpr(expr, loc) = expr;535	// let bp = with_state(|s| s.0.stop_at.borrow().clone());536	Ok(match &**expr {537		Literal(LiteralType::This) => {538			Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)539		}540		Literal(LiteralType::Super) => Val::Obj(541			ctx.super_obj().clone().ok_or(NoSuperFound)?.with_this(542				ctx.this()543					.clone()544					.expect("if super exists - then this should to"),545			),546		),547		Literal(LiteralType::Dollar) => {548			Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)549		}550		Literal(LiteralType::True) => Val::Bool(true),551		Literal(LiteralType::False) => Val::Bool(false),552		Literal(LiteralType::Null) => Val::Null,553		Parened(e) => evaluate(s, ctx, e)?,554		Str(v) => Val::Str(v.clone()),555		Num(v) => Val::new_checked_num(*v)?,556		BinaryOp(v1, o, v2) => evaluate_binary_op_special(s, ctx, v1, *o, v2)?,557		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(s, ctx, v)?)?,558		Var(name) => s.push(559			CallLocation::new(loc),560			|| format!("variable <{}> access", name),561			|| ctx.binding(name.clone())?.evaluate(s.clone()),562		)?,563		Index(value, index) => {564			match (565				evaluate(s.clone(), ctx.clone(), value)?,566				evaluate(s.clone(), ctx, index)?,567			) {568				(Val::Obj(v), Val::Str(key)) => s.push(569					CallLocation::new(loc),570					|| format!("field <{}> access", key),571					|| match v.get(s.clone(), key.clone()) {572						Ok(Some(v)) => Ok(v),573						Ok(None) => throw!(NoSuchField(key.clone())),574						Err(e) if matches!(e.error(), MagicThisFileUsed) => {575							Ok(Val::Str(loc.0.to_string_lossy().into()))576						}577						Err(e) => Err(e),578					},579				)?,580				(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(581					ValType::Obj,582					ValType::Str,583					n.value_type(),584				)),585586				(Val::Arr(v), Val::Num(n)) => {587					if n.fract() > f64::EPSILON {588						throw!(FractionalIndex)589					}590					v.get(s, n as usize)?591						.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?592				}593				(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),594				(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(595					ValType::Arr,596					ValType::Num,597					n.value_type(),598				)),599600				(Val::Str(s), Val::Num(n)) => Val::Str({601					let v: IStr = s602						.chars()603						.skip(n as usize)604						.take(1)605						.collect::<String>()606						.into();607					if v.is_empty() {608						let size = s.chars().count();609						throw!(StringBoundsError(n as usize, size))610					}611					v612				}),613				(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(614					ValType::Str,615					ValType::Num,616					n.value_type(),617				)),618619				(v, _) => throw!(CantIndexInto(v.value_type())),620			}621		}622		LocalExpr(bindings, returned) => {623			let mut new_bindings: GcHashMap<IStr, LazyVal> =624				GcHashMap::with_capacity(bindings.len());625			let fctx = Context::new_future();626			for b in bindings {627				new_bindings.insert(b.name.clone(), evaluate_binding_in_future(b, fctx.clone()));628			}629			let ctx = ctx.extend_bound(new_bindings).into_future(fctx);630			evaluate(s, ctx, &returned.clone())?631		}632		Arr(items) => {633			let mut out = Vec::with_capacity(items.len());634			for item in items {635				// TODO: Implement ArrValue::Lazy with same context for every element?636				#[derive(Trace)]637				struct ArrayElement {638					ctx: Context,639					item: LocExpr,640				}641				impl LazyValValue for ArrayElement {642					fn get(self: Box<Self>, s: State) -> Result<Val> {643						evaluate(s, self.ctx, &self.item)644					}645				}646				out.push(LazyVal::new(TraceBox(Box::new(ArrayElement {647					ctx: ctx.clone(),648					item: item.clone(),649				}))));650			}651			Val::Arr(out.into())652		}653		ArrComp(expr, comp_specs) => {654			let mut out = Vec::new();655			evaluate_comp(s.clone(), ctx, comp_specs, &mut |ctx| {656				out.push(evaluate(s.clone(), ctx, expr)?);657				Ok(())658			})?;659			Val::Arr(ArrValue::Eager(Cc::new(out)))660		}661		Obj(body) => Val::Obj(evaluate_object(s, ctx, body)?),662		ObjExtend(a, b) => evaluate_add_op(663			s.clone(),664			&evaluate(s.clone(), ctx.clone(), a)?,665			&Val::Obj(evaluate_object(s, ctx, b)?),666		)?,667		Apply(value, args, tailstrict) => {668			evaluate_apply(s, ctx, value, args, CallLocation::new(loc), *tailstrict)?669		}670		Function(params, body) => {671			evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())672		}673		Intrinsic(name) => Val::Func(FuncVal::StaticBuiltin(674			BUILTINS675				.with(|b| b.get(name).copied())676				.ok_or_else(|| IntrinsicNotFound(name.clone()))?,677		)),678		IntrinsicThisFile => return Err(MagicThisFileUsed.into()),679		IntrinsicId => Val::Func(FuncVal::identity()),680		AssertExpr(assert, returned) => {681			evaluate_assert(s.clone(), ctx.clone(), assert)?;682			evaluate(s, ctx, returned)?683		}684		ErrorStmt(e) => s.push(685			CallLocation::new(loc),686			|| "error statement".to_owned(),687			|| {688				throw!(RuntimeError(689					evaluate(s.clone(), ctx, e)?.to_string(s.clone())?,690				))691			},692		)?,693		IfElse {694			cond,695			cond_then,696			cond_else,697		} => {698			if s.push(699				CallLocation::new(loc),700				|| "if condition".to_owned(),701				|| bool::from_untyped(evaluate(s.clone(), ctx.clone(), &cond.0)?, s.clone()),702			)? {703				evaluate(s, ctx, cond_then)?704			} else {705				match cond_else {706					Some(v) => evaluate(s, ctx, v)?,707					None => Val::Null,708				}709			}710		}711		Slice(value, desc) => {712			fn parse_idx<T: Typed>(713				loc: CallLocation,714				s: State,715				ctx: &Context,716				expr: &Option<LocExpr>,717				desc: &'static str,718			) -> Result<Option<T>> {719				if let Some(value) = expr {720					Ok(Some(s.push(721						loc,722						|| format!("slice {}", desc),723						|| T::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),724					)?))725				} else {726					Ok(None)727				}728			}729730			let indexable = evaluate(s.clone(), ctx.clone(), value)?;731			let loc = CallLocation::new(loc);732733			let start = parse_idx(loc, s.clone(), &ctx, &desc.start, "start")?;734			let end = parse_idx(loc, s.clone(), &ctx, &desc.end, "end")?;735			let step = parse_idx(loc, s, &ctx, &desc.step, "step")?;736737			std_slice(indexable.into_indexable()?, start, end, step)?738		}739		Import(path) => {740			let tmp = loc.clone().0;741			let mut import_location = tmp.to_path_buf();742			import_location.pop();743			s.push(744				CallLocation::new(loc),745				|| format!("import {:?}", path),746				|| s.import_file(&import_location, path),747			)?748		}749		ImportStr(path) => {750			let tmp = loc.clone().0;751			let mut import_location = tmp.to_path_buf();752			import_location.pop();753			Val::Str(s.import_file_str(&import_location, path)?)754		}755		ImportBin(path) => {756			let tmp = loc.clone().0;757			let mut import_location = tmp.to_path_buf();758			import_location.pop();759			let bytes = s.import_file_bin(&import_location, path)?;760			Val::Arr(ArrValue::Bytes(bytes))761		}762	})763}