git.delta.rocks / jrsonnet / refs/commits / 88a0ba11fe45

difftreelog

source

crates/jrsonnet-evaluator/src/evaluate/mod.rs18.0 KiBsourcehistory
1use gcmodule::{Cc, Trace};2use jrsonnet_interner::IStr;3use jrsonnet_parser::{4	ArgsDesc, AssertStmt, BindSpec, CompSpec, Destruct, Expr, FieldMember, ForSpecData, IfSpecData,5	LiteralType, LocExpr, Member, ObjBody, ParamsDesc,6};7use jrsonnet_types::ValType;89use crate::{10	destructure::evaluate_dest,11	error::Error::*,12	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},13	function::{CallLocation, FuncDesc, FuncVal},14	stdlib::{std_slice, BUILTINS},15	tb, throw,16	typed::Typed,17	val::{ArrValue, Thunk, ThunkValue},18	Bindable, Context, ContextCreator, GcHashMap, LazyBinding, ObjValue, ObjValueBuilder,19	ObjectAssertion, Pending, Result, State, Val,20};21pub mod destructure;22pub mod operator;2324#[allow(clippy::too_many_lines)]25pub fn evaluate_binding(b: BindSpec, cctx: ContextCreator) -> Result<(IStr, LazyBinding)> {26	match b {27		BindSpec::Field {28			into: Destruct::Full(name),29			value,30		} => {31			#[derive(Trace)]32			struct BindableNamedThunk {33				this: Option<ObjValue>,34				super_obj: Option<ObjValue>,3536				cctx: ContextCreator,37				name: IStr,38				value: LocExpr,39			}40			impl ThunkValue for BindableNamedThunk {41				type Output = Val;42				fn get(self: Box<Self>, s: State) -> Result<Val> {43					evaluate_named(44						s.clone(),45						self.cctx.create(s, self.this, self.super_obj)?,46						&self.value,47						self.name,48					)49				}50			}5152			#[derive(Trace)]53			struct BindableNamed {54				cctx: ContextCreator,55				name: IStr,56				value: LocExpr,57			}58			impl Bindable for BindableNamed {59				fn bind(60					&self,61					_: State,62					this: Option<ObjValue>,63					super_obj: Option<ObjValue>,64				) -> Result<Thunk<Val>> {65					Ok(Thunk::new(tb!(BindableNamedThunk {66						this,67						super_obj,6869						cctx: self.cctx.clone(),70						name: self.name.clone(),71						value: self.value.clone(),72					})))73				}74			}7576			Ok((77				name.clone(),78				LazyBinding::Bindable(Cc::new(tb!(BindableNamed {79					cctx,80					name: name.clone(),81					value: value.clone(),82				}))),83			))84		}85		#[cfg(feature = "exp-destruct")]86		BindSpec::Field { into: _, .. } => {87			use crate::throw_runtime;88			throw_runtime!("destructuring is not yet supported here")89		}90		BindSpec::Function {91			name,92			params,93			value,94		} => {95			#[derive(Trace)]96			struct BindableMethodThunk {97				this: Option<ObjValue>,98				super_obj: Option<ObjValue>,99100				cctx: ContextCreator,101				name: IStr,102				params: ParamsDesc,103				value: LocExpr,104			}105			impl ThunkValue for BindableMethodThunk {106				type Output = Val;107				fn get(self: Box<Self>, s: State) -> Result<Val> {108					Ok(evaluate_method(109						self.cctx.create(s, self.this, self.super_obj)?,110						self.name,111						self.params,112						self.value,113					))114				}115			}116117			#[derive(Trace)]118			struct BindableMethod {119				cctx: ContextCreator,120				name: IStr,121				params: ParamsDesc,122				value: LocExpr,123			}124			impl Bindable for BindableMethod {125				fn bind(126					&self,127					_: State,128					this: Option<ObjValue>,129					super_obj: Option<ObjValue>,130				) -> Result<Thunk<Val>> {131					Ok(Thunk::<Val>::new(tb!(BindableMethodThunk {132						this,133						super_obj,134135						cctx: self.cctx.clone(),136						name: self.name.clone(),137						params: self.params.clone(),138						value: self.value.clone(),139					})))140				}141			}142143			let params = params.clone();144145			Ok((146				name.clone(),147				LazyBinding::Bindable(Cc::new(tb!(BindableMethod {148					cctx,149					name: name.clone(),150					params,151					value,152				}))),153			))154		}155	}156}157158pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {159	Val::Func(FuncVal::Normal(Cc::new(FuncDesc {160		name,161		ctx,162		params,163		body,164	})))165}166167pub fn evaluate_field_name(168	s: State,169	ctx: Context,170	field_name: &jrsonnet_parser::FieldName,171) -> Result<Option<IStr>> {172	Ok(match field_name {173		jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),174		jrsonnet_parser::FieldName::Dyn(expr) => s.push(175			CallLocation::new(&expr.1),176			|| "evaluating field name".to_string(),177			|| {178				let value = evaluate(s.clone(), ctx, expr)?;179				if matches!(value, Val::Null) {180					Ok(None)181				} else {182					Ok(Some(IStr::from_untyped(value, s.clone())?))183				}184			},185		)?,186	})187}188189pub fn evaluate_comp(190	s: State,191	ctx: Context,192	specs: &[CompSpec],193	callback: &mut impl FnMut(Context) -> Result<()>,194) -> Result<()> {195	match specs.get(0) {196		None => callback(ctx)?,197		Some(CompSpec::IfSpec(IfSpecData(cond))) => {198			if bool::from_untyped(evaluate(s.clone(), ctx.clone(), cond)?, s.clone())? {199				evaluate_comp(s, ctx, &specs[1..], callback)?;200			}201		}202		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {203			match evaluate(s.clone(), ctx.clone(), expr)? {204				Val::Arr(list) => {205					for item in list.iter(s.clone()) {206						evaluate_comp(207							s.clone(),208							ctx.clone().with_var(var.clone(), item?.clone()),209							&specs[1..],210							callback,211						)?;212					}213				}214				_ => throw!(InComprehensionCanOnlyIterateOverArray),215			}216		}217	}218	Ok(())219}220221#[allow(clippy::too_many_lines)]222pub fn evaluate_member_list_object(s: State, ctx: Context, members: &[Member]) -> Result<ObjValue> {223	let new_bindings = Pending::new();224	let future_this = Pending::new();225	let cctx = ContextCreator(ctx.clone(), new_bindings.clone());226	{227		let mut bindings: GcHashMap<IStr, LazyBinding> = GcHashMap::with_capacity(members.len());228		for r in members229			.iter()230			.filter_map(|m| match m {231				Member::BindStmt(b) => Some(b.clone()),232				_ => None,233			})234			.map(|b| evaluate_binding(b.clone(), cctx.clone()))235		{236			let (n, b) = r?;237			bindings.insert(n, b);238		}239		new_bindings.fill(bindings);240	}241242	let mut builder = ObjValueBuilder::new();243	for member in members.iter() {244		match member {245			Member::Field(FieldMember {246				name,247				plus,248				params: None,249				visibility,250				value,251			}) => {252				#[derive(Trace)]253				struct ObjMemberBinding {254					cctx: ContextCreator,255					value: LocExpr,256					name: IStr,257				}258				impl Bindable for ObjMemberBinding {259					fn bind(260						&self,261						s: State,262						this: Option<ObjValue>,263						super_obj: Option<ObjValue>,264					) -> Result<Thunk<Val>> {265						Ok(Thunk::evaluated(evaluate_named(266							s.clone(),267							self.cctx.create(s, this, super_obj)?,268							&self.value,269							self.name.clone(),270						)?))271					}272				}273274				let name = evaluate_field_name(s.clone(), ctx.clone(), name)?;275				let name = if let Some(name) = name {276					name277				} else {278					continue;279				};280281				builder282					.member(name.clone())283					.with_add(*plus)284					.with_visibility(*visibility)285					.with_location(value.1.clone())286					.bindable(287						s.clone(),288						tb!(ObjMemberBinding {289							cctx: cctx.clone(),290							value: value.clone(),291							name,292						}),293					)?;294			}295			Member::Field(FieldMember {296				name,297				params: Some(params),298				value,299				..300			}) => {301				#[derive(Trace)]302				struct ObjMemberBinding {303					cctx: ContextCreator,304					value: LocExpr,305					params: ParamsDesc,306					name: IStr,307				}308				impl Bindable for ObjMemberBinding {309					fn bind(310						&self,311						s: State,312						this: Option<ObjValue>,313						super_obj: Option<ObjValue>,314					) -> Result<Thunk<Val>> {315						Ok(Thunk::evaluated(evaluate_method(316							self.cctx.create(s, this, super_obj)?,317							self.name.clone(),318							self.params.clone(),319							self.value.clone(),320						)))321					}322				}323324				let name = if let Some(name) = evaluate_field_name(s.clone(), ctx.clone(), name)? {325					name326				} else {327					continue;328				};329330				builder331					.member(name.clone())332					.hide()333					.with_location(value.1.clone())334					.bindable(335						s.clone(),336						tb!(ObjMemberBinding {337							cctx: cctx.clone(),338							value: value.clone(),339							params: params.clone(),340							name,341						}),342					)?;343			}344			Member::BindStmt(_) => {}345			Member::AssertStmt(stmt) => {346				#[derive(Trace)]347				struct ObjectAssert {348					cctx: ContextCreator,349					assert: AssertStmt,350				}351				impl ObjectAssertion for ObjectAssert {352					fn run(353						&self,354						s: State,355						this: Option<ObjValue>,356						super_obj: Option<ObjValue>,357					) -> Result<()> {358						let ctx = self.cctx.create(s.clone(), this, super_obj)?;359						evaluate_assert(s, ctx, &self.assert)360					}361				}362				builder.assert(tb!(ObjectAssert {363					cctx: cctx.clone(),364					assert: stmt.clone(),365				}));366			}367		}368	}369	let this = builder.build();370	future_this.fill(this.clone());371	Ok(this)372}373374pub fn evaluate_object(s: State, ctx: Context, object: &ObjBody) -> Result<ObjValue> {375	Ok(match object {376		ObjBody::MemberList(members) => evaluate_member_list_object(s, ctx, members)?,377		ObjBody::ObjComp(obj) => {378			let future_this = Pending::new();379			let mut builder = ObjValueBuilder::new();380			evaluate_comp(s.clone(), ctx, &obj.compspecs, &mut |ctx| {381				let new_bindings = Pending::new();382				let cctx = ContextCreator(ctx.clone(), new_bindings.clone());383				let mut bindings: GcHashMap<IStr, LazyBinding> =384					GcHashMap::with_capacity(obj.pre_locals.len() + obj.post_locals.len());385				for r in obj386					.pre_locals387					.iter()388					.chain(obj.post_locals.iter())389					.map(|b| evaluate_binding(b.clone(), cctx.clone()))390				{391					let (n, b) = r?;392					bindings.insert(n, b);393				}394				new_bindings.fill(bindings.clone());395				let ctx = ctx.extend_unbound(s.clone(), bindings, None, None, None)?;396				let key = evaluate(s.clone(), ctx.clone(), &obj.key)?;397398				match key {399					Val::Null => {}400					Val::Str(n) => {401						#[derive(Trace)]402						struct ObjCompBinding {403							ctx: Context,404							value: LocExpr,405						}406						impl Bindable for ObjCompBinding {407							fn bind(408								&self,409								s: State,410								this: Option<ObjValue>,411								_super_obj: Option<ObjValue>,412							) -> Result<Thunk<Val>> {413								Ok(Thunk::evaluated(evaluate(414									s,415									self.ctx.clone().extend(GcHashMap::new(), None, this, None),416									&self.value,417								)?))418							}419						}420						builder421							.member(n)422							.with_location(obj.value.1.clone())423							.with_add(obj.plus)424							.bindable(425								s.clone(),426								tb!(ObjCompBinding {427									ctx,428									value: obj.value.clone(),429								}),430							)?;431					}432					v => throw!(FieldMustBeStringGot(v.value_type())),433				}434435				Ok(())436			})?;437438			let this = builder.build();439			future_this.fill(this.clone());440			this441		}442	})443}444445pub fn evaluate_apply(446	s: State,447	ctx: Context,448	value: &LocExpr,449	args: &ArgsDesc,450	loc: CallLocation,451	tailstrict: bool,452) -> Result<Val> {453	let value = evaluate(s.clone(), ctx.clone(), value)?;454	Ok(match value {455		Val::Func(f) => {456			let body = || f.evaluate(s.clone(), ctx, loc, args, tailstrict);457			if tailstrict {458				body()?459			} else {460				s.push(loc, || format!("function <{}> call", f.name()), body)?461			}462		}463		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),464	})465}466467pub fn evaluate_assert(s: State, ctx: Context, assertion: &AssertStmt) -> Result<()> {468	let value = &assertion.0;469	let msg = &assertion.1;470	let assertion_result = s.push(471		CallLocation::new(&value.1),472		|| "assertion condition".to_owned(),473		|| bool::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),474	)?;475	if !assertion_result {476		s.push(477			CallLocation::new(&value.1),478			|| "assertion failure".to_owned(),479			|| {480				if let Some(msg) = msg {481					throw!(AssertionFailed(482						evaluate(s.clone(), ctx, msg)?.to_string(s.clone())?483					));484				}485				throw!(AssertionFailed(Val::Null.to_string(s.clone())?));486			},487		)?;488	}489	Ok(())490}491492pub fn evaluate_named(s: State, ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {493	use Expr::*;494	let LocExpr(raw_expr, _loc) = expr;495	Ok(match &**raw_expr {496		Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),497		_ => evaluate(s, ctx, expr)?,498	})499}500501#[allow(clippy::too_many_lines)]502pub fn evaluate(s: State, ctx: Context, expr: &LocExpr) -> Result<Val> {503	use Expr::*;504	let LocExpr(expr, loc) = expr;505	// let bp = with_state(|s| s.0.stop_at.borrow().clone());506	Ok(match &**expr {507		Literal(LiteralType::This) => {508			Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)509		}510		Literal(LiteralType::Super) => Val::Obj(511			ctx.super_obj().clone().ok_or(NoSuperFound)?.with_this(512				ctx.this()513					.clone()514					.expect("if super exists - then this should to"),515			),516		),517		Literal(LiteralType::Dollar) => {518			Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)519		}520		Literal(LiteralType::True) => Val::Bool(true),521		Literal(LiteralType::False) => Val::Bool(false),522		Literal(LiteralType::Null) => Val::Null,523		Parened(e) => evaluate(s, ctx, e)?,524		Str(v) => Val::Str(v.clone()),525		Num(v) => Val::new_checked_num(*v)?,526		BinaryOp(v1, o, v2) => evaluate_binary_op_special(s, ctx, v1, *o, v2)?,527		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(s, ctx, v)?)?,528		Var(name) => s.push(529			CallLocation::new(loc),530			|| format!("variable <{}> access", name),531			|| ctx.binding(name.clone())?.evaluate(s.clone()),532		)?,533		Index(value, index) => {534			match (535				evaluate(s.clone(), ctx.clone(), value)?,536				evaluate(s.clone(), ctx, index)?,537			) {538				(Val::Obj(v), Val::Str(key)) => s.push(539					CallLocation::new(loc),540					|| format!("field <{}> access", key),541					|| match v.get(s.clone(), key.clone()) {542						Ok(Some(v)) => Ok(v),543						Ok(None) => throw!(NoSuchField(key.clone())),544						Err(e) if matches!(e.error(), MagicThisFileUsed) => {545							Ok(Val::Str(loc.0.to_string_lossy().into()))546						}547						Err(e) => Err(e),548					},549				)?,550				(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(551					ValType::Obj,552					ValType::Str,553					n.value_type(),554				)),555556				(Val::Arr(v), Val::Num(n)) => {557					if n.fract() > f64::EPSILON {558						throw!(FractionalIndex)559					}560					v.get(s, n as usize)?561						.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?562				}563				(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),564				(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(565					ValType::Arr,566					ValType::Num,567					n.value_type(),568				)),569570				(Val::Str(s), Val::Num(n)) => Val::Str({571					let v: IStr = s572						.chars()573						.skip(n as usize)574						.take(1)575						.collect::<String>()576						.into();577					if v.is_empty() {578						let size = s.chars().count();579						throw!(StringBoundsError(n as usize, size))580					}581					v582				}),583				(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(584					ValType::Str,585					ValType::Num,586					n.value_type(),587				)),588589				(v, _) => throw!(CantIndexInto(v.value_type())),590			}591		}592		LocalExpr(bindings, returned) => {593			let mut new_bindings: GcHashMap<IStr, Thunk<Val>> =594				GcHashMap::with_capacity(bindings.len());595			let fctx = Context::new_future();596			for b in bindings {597				evaluate_dest(b, fctx.clone(), &mut new_bindings)?;598			}599			let ctx = ctx.extend_bound(new_bindings).into_future(fctx);600			evaluate(s, ctx, &returned.clone())?601		}602		Arr(items) => {603			let mut out = Vec::with_capacity(items.len());604			for item in items {605				// TODO: Implement ArrValue::Lazy with same context for every element?606				#[derive(Trace)]607				struct ArrayElement {608					ctx: Context,609					item: LocExpr,610				}611				impl ThunkValue for ArrayElement {612					type Output = Val;613					fn get(self: Box<Self>, s: State) -> Result<Val> {614						evaluate(s, self.ctx, &self.item)615					}616				}617				out.push(Thunk::new(tb!(ArrayElement {618					ctx: ctx.clone(),619					item: item.clone(),620				})));621			}622			Val::Arr(out.into())623		}624		ArrComp(expr, comp_specs) => {625			let mut out = Vec::new();626			evaluate_comp(s.clone(), ctx, comp_specs, &mut |ctx| {627				out.push(evaluate(s.clone(), ctx, expr)?);628				Ok(())629			})?;630			Val::Arr(ArrValue::Eager(Cc::new(out)))631		}632		Obj(body) => Val::Obj(evaluate_object(s, ctx, body)?),633		ObjExtend(a, b) => evaluate_add_op(634			s.clone(),635			&evaluate(s.clone(), ctx.clone(), a)?,636			&Val::Obj(evaluate_object(s, ctx, b)?),637		)?,638		Apply(value, args, tailstrict) => {639			evaluate_apply(s, ctx, value, args, CallLocation::new(loc), *tailstrict)?640		}641		Function(params, body) => {642			evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())643		}644		Intrinsic(name) => Val::Func(FuncVal::StaticBuiltin(645			BUILTINS646				.with(|b| b.get(name).copied())647				.ok_or_else(|| IntrinsicNotFound(name.clone()))?,648		)),649		IntrinsicThisFile => return Err(MagicThisFileUsed.into()),650		IntrinsicId => Val::Func(FuncVal::identity()),651		AssertExpr(assert, returned) => {652			evaluate_assert(s.clone(), ctx.clone(), assert)?;653			evaluate(s, ctx, returned)?654		}655		ErrorStmt(e) => s.push(656			CallLocation::new(loc),657			|| "error statement".to_owned(),658			|| {659				throw!(RuntimeError(660					evaluate(s.clone(), ctx, e)?.to_string(s.clone())?,661				))662			},663		)?,664		IfElse {665			cond,666			cond_then,667			cond_else,668		} => {669			if s.push(670				CallLocation::new(loc),671				|| "if condition".to_owned(),672				|| bool::from_untyped(evaluate(s.clone(), ctx.clone(), &cond.0)?, s.clone()),673			)? {674				evaluate(s, ctx, cond_then)?675			} else {676				match cond_else {677					Some(v) => evaluate(s, ctx, v)?,678					None => Val::Null,679				}680			}681		}682		Slice(value, desc) => {683			fn parse_idx<T: Typed>(684				loc: CallLocation,685				s: State,686				ctx: &Context,687				expr: &Option<LocExpr>,688				desc: &'static str,689			) -> Result<Option<T>> {690				if let Some(value) = expr {691					Ok(Some(s.push(692						loc,693						|| format!("slice {}", desc),694						|| T::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),695					)?))696				} else {697					Ok(None)698				}699			}700701			let indexable = evaluate(s.clone(), ctx.clone(), value)?;702			let loc = CallLocation::new(loc);703704			let start = parse_idx(loc, s.clone(), &ctx, &desc.start, "start")?;705			let end = parse_idx(loc, s.clone(), &ctx, &desc.end, "end")?;706			let step = parse_idx(loc, s, &ctx, &desc.step, "step")?;707708			std_slice(indexable.into_indexable()?, start, end, step)?709		}710		Import(path) => {711			let tmp = loc.clone().0;712			let mut import_location = tmp.to_path_buf();713			import_location.pop();714			s.push(715				CallLocation::new(loc),716				|| format!("import {:?}", path),717				|| s.import_file(&import_location, path),718			)?719		}720		ImportStr(path) => {721			let tmp = loc.clone().0;722			let mut import_location = tmp.to_path_buf();723			import_location.pop();724			Val::Str(s.import_file_str(&import_location, path)?)725		}726		ImportBin(path) => {727			let tmp = loc.clone().0;728			let mut import_location = tmp.to_path_buf();729			import_location.pop();730			let bytes = s.import_file_bin(&import_location, path)?;731			Val::Arr(ArrValue::Bytes(bytes))732		}733	})734}