1use std::rc::Rc;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_ir::ImportKind;6use jrsonnet_types::ValType;78use self::{9 compspec::{evaluate_arr_comp, evaluate_obj_comp},10 destructure::{evaluate_local_expr, evaluate_locals_unbound},11 operator::evaluate_binary_op_special,12};13use crate::{14 Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Result, ResultExt as _, SupThis,15 Unbound, Val,16 analyze::{17 ClosureShape, LArgsDesc, LAssertStmt, LExpr, LFieldMember, LFieldName, LFunction,18 LIndexPart, LObjAsserts, LObjBody, LObjMembers, LSlot,19 },20 arr::ArrValue,21 bail,22 error::{ErrorKind::*, suggest_object_fields},23 evaluate::{destructure::fill_letrec_binds, operator::evaluate_unary_op},24 function::{CallLocation, FuncDesc, FuncVal, prepared::PreparedFuncVal},25 in_frame,26 typed::{BoundedUsize, FromUntyped as _},27 val::{CachedUnbound, Thunk},28 with_state,29};3031pub mod compspec;32pub mod destructure;33pub mod operator;3435363738const RED_ZONE: usize = 100 * 1024;39404142const STACK_PER_RECURSION: usize = 1024 * 1024;43444546474849#[inline]50pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {51 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)52}5354pub fn evaluate_trivial(expr: &LExpr) -> Option<Val> {55 56 Some(match expr {57 LExpr::Str(s) => Val::string(s.clone()),58 LExpr::Num(n) => Val::Num(*n),59 LExpr::Bool(false) => Val::Bool(false),60 LExpr::Bool(true) => Val::Bool(true),61 LExpr::Null => Val::Null,62 _ => return None,63 })64}6566pub fn evaluate_method(ctx: Context, name: IStr, func: &Rc<LFunction>) -> Val {67 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {68 name,69 body_captures: ctx.pack_captures_sup_this(&func.body_shape),70 func: func.clone(),71 })))72}7374pub fn evaluate_field_name(ctx: Context, field_name: &LFieldName) -> Result<Option<IStr>> {75 Ok(match field_name {76 LFieldName::Fixed(n) => Some(n.clone()),77 LFieldName::Dyn(expr) => in_frame(78 79 CallLocation::native(),80 || "evaluating field name".to_string(),81 || {82 let v = evaluate(ctx.clone(), expr)?;83 Ok(if matches!(v, Val::Null) {84 None85 } else {86 Some(IStr::from_untyped(v)?)87 })88 },89 )?,90 })91}9293pub fn evaluate_thunk(ctx: Context, expr: Rc<LExpr>, tailstrict: bool) -> Result<Thunk<Val>> {94 match &*expr {95 LExpr::Slot(LSlot::Local(i)) => return Ok(ctx.local(*i)),96 LExpr::Slot(LSlot::Capture(i)) => return Ok(ctx.capture(*i)),97 _ => {98 if let Some(v) = evaluate_trivial(&expr) {99 return Ok(Thunk::evaluated(v));100 }101 }102 }103 Ok(if tailstrict {104 Thunk::evaluated(evaluate(ctx, &expr)?)105 } else {106 Thunk!(move || { evaluate(ctx, &expr) })107 })108}109110mod names {111 use crate::names;112113 names! {114 anonymous: "anonymous",115 }116}117118#[allow(clippy::too_many_lines)]119pub fn evaluate(ctx: Context, expr: &LExpr) -> Result<Val> {120 Ok(match expr {121 LExpr::Null => Val::Null,122 LExpr::Bool(b) => Val::Bool(*b),123 LExpr::Str(s) => Val::string(s.clone()),124 LExpr::Num(n) => Val::Num(*n),125 LExpr::Slot(slot) => ctx.slot(*slot).evaluate()?,126 LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),127 LExpr::Arr { shape, items } => Val::Arr(ArrValue::expr(ctx, shape, items.clone())),128 LExpr::UnaryOp(op, value) => {129 let value = evaluate(ctx, value)?;130 evaluate_unary_op(*op, &value)?131 }132 LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,133 LExpr::LocalExpr(local_expr) => evaluate_local_expr(ctx, local_expr)?,134 LExpr::IfElse {135 cond,136 cond_then,137 cond_else,138 } => {139 let cond_val = evaluate(ctx.clone(), cond)?;140 let Val::Bool(b) = cond_val else {141 bail!(TypeMismatch(142 "if condition",143 vec![ValType::Bool],144 cond_val.value_type()145 ))146 };147 if b {148 evaluate(ctx, cond_then)?149 } else if let Some(e) = cond_else {150 evaluate(ctx, e)?151 } else {152 Val::Null153 }154 }155 LExpr::Error(s, e) => in_frame(156 CallLocation::new(s),157 || "error statement".to_owned(),158 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),159 )?,160 LExpr::AssertExpr { assert, rest } => {161 evaluate_assert(ctx.clone(), assert)?;162 evaluate(ctx, rest)?163 }164165 LExpr::Function(func) => evaluate_method(166 ctx,167 func.name.clone().unwrap_or_else(names::anonymous),168 func,169 ),170 LExpr::IdentityFunction => Val::Func(FuncVal::identity()),171 LExpr::Apply {172 applicable,173 args,174 tailstrict,175 } => evaluate_apply(176 ctx,177 applicable,178 args,179 CallLocation::new(&args.span),180 *tailstrict,181 )?,182 LExpr::Index { indexable, parts } => evaluate_index(ctx, indexable, parts)?,183 LExpr::Obj(body) => evaluate_obj_body(None, ctx, body)?,184 LExpr::ObjExtend(lhs, body) => {185 let lhs_val = evaluate(ctx.clone(), lhs)?;186 let Val::Obj(lhs_obj) = lhs_val else {187 bail!(TypeMismatch(188 "object extend lhs",189 vec![ValType::Obj],190 lhs_val.value_type(),191 ))192 };193 evaluate_obj_body(Some(lhs_obj), ctx, body)?194 }195 LExpr::ArrComp(comp) => evaluate_arr_comp(ctx, comp)?,196 LExpr::Slice(slice) => {197 let val = evaluate(ctx.clone(), &slice.value)?;198 let indexable = val.into_indexable()?;199 let start = slice200 .start201 .as_ref()202 .map(|e| evaluate(ctx.clone(), e))203 .transpose()?204 .map(|v| -> Result<i32> { i32::from_untyped(v).description("slice start value") })205 .transpose()?;206 let end = slice207 .end208 .as_ref()209 .map(|e| evaluate(ctx.clone(), e))210 .transpose()?211 .map(|v| -> Result<i32> { i32::from_untyped(v).description("slice end value") })212 .transpose()?;213 let step = slice214 .step215 .as_ref()216 .map(|e| evaluate(ctx, e))217 .transpose()?218 .map(|v| -> Result<BoundedUsize<1, { i32::MAX as usize }>> {219 BoundedUsize::from_untyped(v).description("slice step value")220 })221 .transpose()?;222 Val::from(indexable.slice32(start, end, step)?)223 }224 LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super().ok_or(NoSuperFound)?),225 LExpr::Import {226 kind,227 kind_span,228 path,229 } => with_state(|state| {230 let resolved = state.resolve_from(kind_span.0.source_path(), &path.clone())?;231 Ok::<_, Error>(match kind.value {232 ImportKind::Normal => in_frame(233 CallLocation::new(&kind.span),234 || "import".to_string(),235 || state.import_resolved(resolved),236 )?,237 ImportKind::Str => Val::string(state.import_resolved_str(resolved)?),238 ImportKind::Bin => Val::arr(state.import_resolved_bin(resolved)?),239 })240 })?,241 })242}243244fn evaluate_apply(245 ctx: Context,246 applicable: &LExpr,247 args: &LArgsDesc,248 loc: CallLocation<'_>,249 tailstrict: bool,250) -> Result<Val> {251 let func_val = evaluate(ctx.clone(), applicable)?;252 let Val::Func(func) = func_val else {253 bail!(OnlyFunctionsCanBeCalledGot(func_val.value_type()))254 };255256 if func.is_identity() && args.names.is_empty() && args.unnamed.len() == 1 {257 return evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?.evaluate();258 }259260 let name = func.name();261262 if args.names.is_empty() && args.unnamed.len() == 1 && func.params().len() == 1 {263 use crate::function::prepared::PreparedCall;264 let prepared_inline = PreparedCall::empty();265 let arg = evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?;266 let arg_slice = std::slice::from_ref(&arg);267 return in_frame(268 loc,269 || format!("function <{name}> call"),270 || {271 func.evaluate_prepared(272 &prepared_inline,273 CallLocation::native(),274 arg_slice,275 &[],276 tailstrict,277 )278 },279 );280 }281282 let unnamed = args283 .unnamed284 .iter()285 .cloned()286 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))287 .collect::<Result<Vec<_>>>()?;288289 290 291 if args.names.is_empty() && unnamed.len() == func.params().len() {292 use crate::function::prepared::PreparedCall;293 let prepared_inline = PreparedCall::empty();294 return in_frame(295 loc,296 || format!("function <{name}> call"),297 || {298 func.evaluate_prepared(299 &prepared_inline,300 CallLocation::native(),301 &unnamed,302 &[],303 tailstrict,304 )305 },306 );307 }308309 let named = args310 .values311 .iter()312 .cloned()313 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))314 .collect::<Result<Vec<_>>>()?;315 let prepare = PreparedFuncVal::new(func, unnamed.len(), &args.names)316 .with_description_src(loc, || format!("function <{name}> preparation"))?;317 in_frame(318 loc,319 || format!("function <{name}> call"),320 || prepare.call(CallLocation::native(), &unnamed, &named),321 )322}323324#[allow(clippy::too_many_lines)]325fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {326 let mut parts = parts.iter();327 let mut indexable = if matches!(indexable, LExpr::Super) {328 let part = parts.next().expect("at least part should exist");329 330 331 let sup_this = ctx.try_sup_this()?;332333 if !sup_this.has_super() {334 #[cfg(feature = "exp-null-coaelse")]335 if part.null_coaelse {336 return Ok(Val::Null);337 }338 bail!(NoSuperFound);339 }340 let name = evaluate(ctx.clone(), &part.value)?;341342 let Val::Str(name) = name else {343 bail!(ValueIndexMustBeTypeGot(344 ValType::Obj,345 ValType::Str,346 name.value_type(),347 ))348 };349350 let name = name.into_flat();351 match sup_this352 .get_super(name.clone())353 .with_description_src(&part.span, || format!("super field <{name}> access"))?354 {355 Some(v) => v,356 #[cfg(feature = "exp-null-coaelse")]357 None if part.null_coaelse => return Ok(Val::Null),358 None => {359 let suggestions = suggest_object_fields(360 &sup_this.standalone_super().expect("super exists"),361 name.clone(),362 );363 bail!(NoSuchField(name, suggestions))364 }365 }366 } else {367 evaluate(ctx.clone(), indexable)?368 };369370 for part in parts {371 let ctx = ctx.clone();372 let loc = CallLocation::new(&part.span);373 let value = indexable;374 let key_val = evaluate(ctx, &part.value)?;375 indexable = match (&value, &key_val) {376 (Val::Obj(obj), Val::Str(key)) => {377 let key = key.clone().into_flat();378 match obj379 .get(key.clone())380 .with_description_src(loc, || format!("field <{key}> access"))?381 {382 Some(v) => v,383 #[cfg(feature = "exp-null-coaelse")]384 None if part.null_coaelse => return Ok(Val::Null),385 None => {386 return Err(Error::from(NoSuchField(387 key.clone(),388 suggest_object_fields(obj, key.clone()),389 )))390 .with_description_src(loc, || format!("field <{key}> access"));391 }392 }393 }394 (Val::Arr(arr), Val::Num(idx)) => {395 let n = idx.get();396 if n.fract() > f64::EPSILON {397 bail!(FractionalIndex)398 }399 let len = arr.len32();400 if n < 0.0 || n > f64::from(len) {401 bail!(ArrayBoundsError(n, len));402 }403 #[expect(404 clippy::cast_possible_truncation,405 clippy::cast_sign_loss,406 reason = "n is checked range"407 )]408 let i = n as u32;409 arr.get32(i)410 .with_description_src(loc, || format!("element <{i}> access"))?411 .ok_or_else(|| ArrayBoundsError(n, len))?412 }413 (Val::Str(s), Val::Num(idx)) => {414 let n = idx.get();415 if n.fract() > f64::EPSILON {416 bail!(FractionalIndex)417 }418 #[expect(419 clippy::cast_possible_truncation,420 clippy::cast_sign_loss,421 reason = "n is checked positive, overflow will truncate as expected"422 )]423 let i = n as usize;424 let flat = s.clone().into_flat();425 #[allow(clippy::cast_possible_truncation, reason = "string is max 4g")]426 if n >= 0.0427 && n <= f64::from(u32::MAX)428 && let Some(char) = flat.chars().nth(i)429 {430 Val::string(char)431 } else {432 let len = flat.chars().count();433 bail!(StringBoundsError(n, len as u32))434 }435 }436 #[cfg(feature = "exp-null-coaelse")]437 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),438 _ => bail!(ValueIndexMustBeTypeGot(439 value.value_type(),440 ValType::Str,441 key_val.value_type()442 )),443 };444 }445 Ok(indexable)446}447448fn evaluate_obj_body(super_obj: Option<ObjValue>, ctx: Context, body: &LObjBody) -> Result<Val> {449 match body {450 LObjBody::MemberList(members) => evaluate_obj_members(super_obj, ctx, members),451 LObjBody::ObjComp(comp) => evaluate_obj_comp(super_obj, ctx, comp),452 }453}454455pub fn evaluate_field_member_unbound<B: Unbound<Bound = Context> + Clone>(456 builder: &mut ObjValueBuilder,457 ctx: Context,458 uctx: B,459 field: &LFieldMember,460) -> Result<()> {461 #[derive(Trace)]462 struct UnboundValue<B: Trace> {463 uctx: B,464 value: Rc<(ClosureShape, LExpr)>,465 name: IStr,466 }467 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {468 type Bound = Val;469 fn bind(&self, sup_this: SupThis) -> Result<Val> {470 let a_ctx = self.uctx.bind(sup_this)?;471 let b_ctx = Context::enter_using(&a_ctx, &self.value.0);472 evaluate(b_ctx, &self.value.1)473 }474 }475476 let LFieldMember {477 name,478 plus,479 visibility,480 value,481 } = field;482 let Some(name) = evaluate_field_name(ctx, name)? else {483 return Ok(());484 };485486 builder487 .field(name.clone())488 .with_add(*plus)489 .with_visibility(*visibility)490 .bindable(UnboundValue {491 uctx,492 value: value.clone(),493 name,494 })495}496pub fn evaluate_field_member_static(497 builder: &mut ObjValueBuilder,498 field_ctx: Context,499 value_ctx: Context,500 field: &LFieldMember,501) -> Result<()> {502 let LFieldMember {503 name,504 plus,505 visibility,506 value,507 } = field;508 let Some(name) = evaluate_field_name(field_ctx, name)? else {509 return Ok(());510 };511512 let env = Context::enter_using(&value_ctx, &value.0);513 let value = value.clone();514 builder515 .field(name)516 .with_add(*plus)517 .with_visibility(*visibility)518 .try_thunk(Thunk!(move || evaluate(env, &value.1)))?;519 Ok(())520}521522fn evaluate_obj_members(523 super_obj: Option<ObjValue>,524 ctx: Context,525 members: &LObjMembers,526) -> Result<Val> {527 let mut builder = ObjValueBuilder::with_capacity(members.fields.len());528 if let Some(sup) = super_obj {529 builder.with_super(sup);530 }531532 let needs_unbound = members.this.is_some() || members.uses_super;533534 if needs_unbound {535 let uctx = CachedUnbound::new(evaluate_locals_unbound(536 &ctx,537 &members.frame_shape,538 members.this,539 members.locals.clone(),540 ));541 for field in &members.fields {542 evaluate_field_member_unbound(&mut builder, ctx.clone(), uctx.clone(), field)?;543 }544 if let Some(asserts_block) = &members.asserts {545 builder.assert(evaluate_object_assertions_unbound(546 uctx,547 asserts_block.clone(),548 ));549 }550 } else {551 let a_ctx = ctx552 .pack_captures_sup_this(&members.frame_shape)553 .enter(|fill, ctx| {554 fill_letrec_binds(fill, ctx, &members.locals);555 });556 for field in &members.fields {557 evaluate_field_member_static(&mut builder, ctx.clone(), a_ctx.clone(), field)?;558 }559 if let Some(asserts_block) = &members.asserts {560 builder.assert(evaluate_object_assertions_static(561 a_ctx,562 asserts_block.clone(),563 ));564 }565 }566567 Ok(Val::Obj(builder.build()))568}569570pub fn evaluate_assert(ctx: Context, assertion: &LAssertStmt) -> Result<()> {571 let LAssertStmt { cond, message } = assertion;572 let assertion_result = in_frame(573 CallLocation::new(&cond.span),574 || "assertion condition".to_owned(),575 || bool::from_untyped(evaluate(ctx.clone(), cond)?),576 )?;577 if !assertion_result {578 in_frame(579 CallLocation::new(&cond.span),580 || "assertion failure".to_owned(),581 || {582 if let Some(msg) = message {583 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));584 }585 bail!(AssertionFailed(Val::Null.to_string()?));586 },587 )?;588 }589 Ok(())590}591592fn evaluate_object_assertions_unbound<B: Unbound<Bound = Context>>(593 uctx: B,594 asserts: Rc<LObjAsserts>,595) -> impl ObjectAssertion {596 #[derive(Trace)]597 struct ObjectAssert<B: Trace> {598 uctx: B,599 asserts: Rc<LObjAsserts>,600 }601 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {602 fn run(&self, sup_this: SupThis) -> Result<()> {603 let a_ctx = self.uctx.bind(sup_this)?;604 let assert_env = Context::enter_using(&a_ctx, &self.asserts.shape);605 for assert in &self.asserts.asserts {606 evaluate_assert(assert_env.clone(), assert)?;607 }608 Ok(())609 }610 }611 ObjectAssert { uctx, asserts }612}613fn evaluate_object_assertions_static(614 a_ctx: Context,615 asserts: Rc<LObjAsserts>,616) -> impl ObjectAssertion {617 #[derive(Trace)]618 struct ObjectAssert {619 assert_env: Context,620 asserts: Rc<LObjAsserts>,621 }622 impl ObjectAssertion for ObjectAssert {623 fn run(&self, _sup_this: SupThis) -> Result<()> {624 for assert in &self.asserts.asserts {625 evaluate_assert(self.assert_env.clone(), assert)?;626 }627 Ok(())628 }629 }630 let assert_env = Context::enter_using(&a_ctx, &asserts.shape);631 ObjectAssert {632 assert_env,633 asserts,634 }635}