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 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 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}