git.delta.rocks / jrsonnet / refs/commits / 11643ec5f009

difftreelog

source

crates/jrsonnet-evaluator/src/evaluate/mod.rs18.6 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	builtin::{std_slice, BUILTINS},11	error::Error::*,12	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},13	function::CallLocation,14	gc::TraceBox,15	throw,16	typed::Typed,17	val::{ArrValue, FuncDesc, FuncVal, 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					|| {572						if let Some(v) = v.get(s.clone(), key.clone())? {573							Ok(v)574						} else {575							throw!(NoSuchField(key.clone()))576						}577					},578				)?,579				(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(580					ValType::Obj,581					ValType::Str,582					n.value_type(),583				)),584585				(Val::Arr(v), Val::Num(n)) => {586					if n.fract() > f64::EPSILON {587						throw!(FractionalIndex)588					}589					v.get(s, n as usize)?590						.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?591				}592				(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),593				(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(594					ValType::Arr,595					ValType::Num,596					n.value_type(),597				)),598599				(Val::Str(s), Val::Num(n)) => Val::Str({600					let v: IStr = s601						.chars()602						.skip(n as usize)603						.take(1)604						.collect::<String>()605						.into();606					if v.is_empty() {607						let size = s.chars().count();608						throw!(StringBoundsError(n as usize, size))609					}610					v611				}),612				(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(613					ValType::Str,614					ValType::Num,615					n.value_type(),616				)),617618				(v, _) => throw!(CantIndexInto(v.value_type())),619			}620		}621		LocalExpr(bindings, returned) => {622			let mut new_bindings: GcHashMap<IStr, LazyVal> =623				GcHashMap::with_capacity(bindings.len());624			let fctx = Context::new_future();625			for b in bindings {626				new_bindings.insert(b.name.clone(), evaluate_binding_in_future(b, fctx.clone()));627			}628			let ctx = ctx.extend_bound(new_bindings).into_future(fctx);629			evaluate(s, ctx, &returned.clone())?630		}631		Arr(items) => {632			let mut out = Vec::with_capacity(items.len());633			for item in items {634				// TODO: Implement ArrValue::Lazy with same context for every element?635				#[derive(Trace)]636				struct ArrayElement {637					ctx: Context,638					item: LocExpr,639				}640				impl LazyValValue for ArrayElement {641					fn get(self: Box<Self>, s: State) -> Result<Val> {642						evaluate(s, self.ctx, &self.item)643					}644				}645				out.push(LazyVal::new(TraceBox(Box::new(ArrayElement {646					ctx: ctx.clone(),647					item: item.clone(),648				}))));649			}650			Val::Arr(out.into())651		}652		ArrComp(expr, comp_specs) => {653			let mut out = Vec::new();654			evaluate_comp(s.clone(), ctx, comp_specs, &mut |ctx| {655				out.push(evaluate(s.clone(), ctx, expr)?);656				Ok(())657			})?;658			Val::Arr(ArrValue::Eager(Cc::new(out)))659		}660		Obj(body) => Val::Obj(evaluate_object(s, ctx, body)?),661		ObjExtend(a, b) => evaluate_add_op(662			s.clone(),663			&evaluate(s.clone(), ctx.clone(), a)?,664			&Val::Obj(evaluate_object(s, ctx, b)?),665		)?,666		Apply(value, args, tailstrict) => {667			evaluate_apply(s, ctx, value, args, CallLocation::new(loc), *tailstrict)?668		}669		Function(params, body) => {670			evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())671		}672		Intrinsic(name) => Val::Func(FuncVal::StaticBuiltin(673			BUILTINS674				.with(|b| b.get(name).copied())675				.ok_or_else(|| IntrinsicNotFound(name.clone()))?,676		)),677		AssertExpr(assert, returned) => {678			evaluate_assert(s.clone(), ctx.clone(), assert)?;679			evaluate(s, ctx, returned)?680		}681		ErrorStmt(e) => s.push(682			CallLocation::new(loc),683			|| "error statement".to_owned(),684			|| {685				throw!(RuntimeError(686					evaluate(s.clone(), ctx, e)?.to_string(s.clone())?,687				))688			},689		)?,690		IfElse {691			cond,692			cond_then,693			cond_else,694		} => {695			if s.push(696				CallLocation::new(loc),697				|| "if condition".to_owned(),698				|| bool::from_untyped(evaluate(s.clone(), ctx.clone(), &cond.0)?, s.clone()),699			)? {700				evaluate(s, ctx, cond_then)?701			} else {702				match cond_else {703					Some(v) => evaluate(s, ctx, v)?,704					None => Val::Null,705				}706			}707		}708		Slice(value, desc) => {709			fn parse_idx<T: Typed>(710				loc: CallLocation,711				s: State,712				ctx: &Context,713				expr: &Option<LocExpr>,714				desc: &'static str,715			) -> Result<Option<T>> {716				if let Some(value) = expr {717					Ok(Some(s.push(718						loc,719						|| format!("slice {}", desc),720						|| T::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),721					)?))722				} else {723					Ok(None)724				}725			}726727			let indexable = evaluate(s.clone(), ctx.clone(), value)?;728			let loc = CallLocation::new(loc);729730			let start = parse_idx(loc, s.clone(), &ctx, &desc.start, "start")?;731			let end = parse_idx(loc, s.clone(), &ctx, &desc.end, "end")?;732			let step = parse_idx(loc, s, &ctx, &desc.step, "step")?;733734			std_slice(indexable.into_indexable()?, start, end, step)?735		}736		Import(path) => {737			let tmp = loc.clone().0;738			let mut import_location = tmp.to_path_buf();739			import_location.pop();740			s.push(741				CallLocation::new(loc),742				|| format!("import {:?}", path),743				|| s.import_file(&import_location, path),744			)?745		}746		ImportStr(path) => {747			let tmp = loc.clone().0;748			let mut import_location = tmp.to_path_buf();749			import_location.pop();750			Val::Str(s.import_file_str(&import_location, path)?)751		}752		ImportBin(path) => {753			let tmp = loc.clone().0;754			let mut import_location = tmp.to_path_buf();755			import_location.pop();756			let bytes = s.import_file_bin(&import_location, path)?;757			Val::Arr(ArrValue::Bytes(bytes))758		}759	})760}