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_locals, 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 LArgsDesc, LAssertStmt, LExpr, LFieldMember, LFieldName, LFunction, LIndexPart, LObjBody,18 LObjMembers,19 },20 bail,21 error::{ErrorKind::*, suggest_object_fields},22 evaluate::operator::evaluate_unary_op,23 function::{CallLocation, FuncDesc, FuncVal, prepared::PreparedFuncVal},24 in_frame, runtime_error,25 typed::FromUntyped as _,26 val::{CachedUnbound, Thunk},27 with_state,28};2930pub mod compspec;31pub mod destructure;32pub mod operator;3334353637const RED_ZONE: usize = 100 * 1024;38394041const STACK_PER_RECURSION: usize = 1024 * 1024;42434445464748#[inline]49pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {50 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)51}5253pub fn evaluate_trivial(expr: &LExpr) -> Option<Val> {54 55 Some(match expr {56 LExpr::Str(s) => Val::string(s.clone()),57 LExpr::Num(n) => Val::Num(*n),58 LExpr::Bool(false) => Val::Bool(false),59 LExpr::Bool(true) => Val::Bool(true),60 LExpr::Null => Val::Null,61 _ => return None,62 })63}646566pub fn evaluate_method(ctx: Context, name: IStr, func: &Rc<LFunction>) -> Val {67 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {68 name,69 ctx,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 Ok(if tailstrict {95 Thunk::evaluated(evaluate(ctx, &expr)?)96 } else {97 Thunk!(move || { evaluate(ctx, &expr) })98 })99}100101mod names {102 use crate::names;103104 names! {105 anonymous: "anonymous",106 }107}108109pub fn evaluate_named(name: &IStr, ctx: Context, expr: &LExpr) -> Result<Val> {110 if let LExpr::Function(f) = &expr {111 return Ok(evaluate_method(112 ctx,113 f.name.clone().unwrap_or_else(|| name.clone()),114 f,115 ));116 }117 evaluate(ctx, expr)118}119120pub fn evaluate(ctx: Context, expr: &LExpr) -> Result<Val> {121 Ok(match expr {122 LExpr::Null => Val::Null,123 LExpr::Bool(b) => Val::Bool(*b),124 LExpr::Str(s) => Val::string(s.clone()),125 LExpr::Num(n) => Val::Num(*n),126 LExpr::Local(id) => {127 let Some(thunk) = ctx.binding(*id) else {128 bail!("should not happen: unbound local {id:?}");129 };130 thunk.evaluate()?131 }132 LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),133 LExpr::Arr(items) => Val::Arr(crate::arr::ArrValue::expr(ctx, items.clone())),134 LExpr::UnaryOp(op, value) => {135 let value = evaluate(ctx, value)?;136 evaluate_unary_op(*op, &value)?137 }138 LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,139 LExpr::LocalExpr { binds, body } => {140 let ctx = evaluate_locals(ctx, binds);141 evaluate(ctx, body)?142 }143 LExpr::IfElse {144 cond,145 cond_then,146 cond_else,147 } => {148 let cond_val = evaluate(ctx.clone(), cond)?;149 let Val::Bool(b) = cond_val else {150 bail!(TypeMismatch(151 "if condition",152 vec![ValType::Bool],153 cond_val.value_type()154 ))155 };156 if b {157 evaluate(ctx, cond_then)?158 } else if let Some(e) = cond_else {159 evaluate(ctx, e)?160 } else {161 Val::Null162 }163 }164 LExpr::Error(s, e) => in_frame(165 CallLocation::new(s),166 || "error statement".to_owned(),167 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),168 )?,169 LExpr::AssertExpr { assert, rest } => {170 evaluate_assert(ctx.clone(), assert)?;171 evaluate(ctx, rest)?172 }173174 LExpr::Function(func) => evaluate_method(175 ctx,176 func.name.clone().unwrap_or_else(names::anonymous),177 func,178 ),179 LExpr::Apply {180 applicable,181 args,182 tailstrict,183 } => evaluate_apply(184 ctx,185 applicable,186 args,187 CallLocation::new(&args.span),188 *tailstrict,189 )?,190 LExpr::Index { indexable, parts } => evaluate_index(ctx, indexable, parts)?,191 LExpr::Obj(body) => evaluate_obj_body(None, ctx, body)?,192 LExpr::ObjExtend(lhs, body) => {193 let lhs_val = evaluate(ctx.clone(), lhs)?;194 let Val::Obj(lhs_obj) = lhs_val else {195 bail!(TypeMismatch(196 "object extend lhs",197 vec![ValType::Obj],198 lhs_val.value_type(),199 ))200 };201 evaluate_obj_body(Some(lhs_obj), ctx, body)?202 }203 LExpr::ArrComp(comp) => evaluate_arr_comp(ctx, comp)?,204 LExpr::Slice(slice) => {205 use crate::typed::BoundedUsize;206 let val = evaluate(ctx.clone(), &slice.value)?;207 let indexable = val.into_indexable()?;208 let start = slice209 .start210 .as_ref()211 .map(|e| evaluate(ctx.clone(), e))212 .transpose()?213 .map(|v| -> Result<i32> {214 v.as_num()215 .ok_or_else(|| {216 TypeMismatch("slice start", vec![ValType::Num], v.value_type()).into()217 })218 .map(|n| n as i32)219 })220 .transpose()?;221 let end = slice222 .end223 .as_ref()224 .map(|e| evaluate(ctx.clone(), e))225 .transpose()?226 .map(|v| -> Result<i32> {227 v.as_num()228 .ok_or_else(|| {229 TypeMismatch("slice end", vec![ValType::Num], v.value_type()).into()230 })231 .map(|n| n as i32)232 })233 .transpose()?;234 let step = slice235 .step236 .as_ref()237 .map(|e| evaluate(ctx, e))238 .transpose()?239 .map(|v| -> Result<BoundedUsize<1, { i32::MAX as usize }>> {240 let n = v.as_num().ok_or_else(|| -> crate::Error {241 TypeMismatch("slice step", vec![ValType::Num], v.value_type()).into()242 })?;243 BoundedUsize::new(n as usize)244 .ok_or_else(|| runtime_error!("slice step must be >= 1"))245 })246 .transpose()?;247 Val::from(indexable.slice(start, end, step)?)248 }249 LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super()?),250 LExpr::Import {251 kind,252 kind_span,253 path,254 } => with_state(|state| {255 let resolved = state.resolve_from(kind_span.0.source_path(), &path.clone())?;256 Ok::<_, Error>(match kind.value {257 ImportKind::Normal => in_frame(258 CallLocation::new(&kind.span),259 || "import".to_string(),260 || state.import_resolved(resolved),261 )?,262 ImportKind::Str => Val::string(state.import_resolved_str(resolved)?),263 ImportKind::Bin => Val::arr(state.import_resolved_bin(resolved)?),264 })265 })?,266 })267}268269fn evaluate_apply(270 ctx: Context,271 applicable: &LExpr,272 args: &LArgsDesc,273 loc: CallLocation<'_>,274 tailstrict: bool,275) -> Result<Val> {276 let func_val = evaluate(ctx.clone(), applicable)?;277 let Val::Func(func) = func_val else {278 bail!(OnlyFunctionsCanBeCalledGot(func_val.value_type()))279 };280281 let name = func.name();282 let unnamed = args283 .unnamed284 .iter()285 .cloned()286 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))287 .collect::<Result<Vec<_>>>()?;288289 let named = args290 .values291 .iter()292 .cloned()293 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))294 .collect::<Result<Vec<_>>>()?;295 let prepare = PreparedFuncVal::new(func, unnamed.len(), &args.names)296 .with_description_src(loc, || format!("function <{name}> preparation"))?;297 in_frame(298 loc,299 || format!("function <{name}> call"),300 || prepare.call(CallLocation::native(), &unnamed, &named),301 )302}303304fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {305 let mut value = if let LExpr::Super = indexable {306 let sup_this = ctx.try_sup_this()?;307 308 if parts.is_empty() {309 bail!(RuntimeError("super requires an index".into()))310 }311 let key_val = evaluate(ctx.clone(), &parts[0].value)?;312 let Val::Str(key) = &key_val else {313 bail!(ValueIndexMustBeTypeGot(314 ValType::Obj,315 ValType::Str,316 key_val.value_type(),317 ))318 };319 let field = key.clone().into_flat();320 if let Some(v) = sup_this.get_super(field.clone())? {321 322 let mut value = v;323 for part in &parts[1..] {324 value = index_val(ctx.clone(), CallLocation::new(&part.span), value, part)?;325 }326 return Ok(value);327 }328 let suggestions = suggest_object_fields(sup_this.this(), field.clone());329 bail!(NoSuchField(field, suggestions))330 } else {331 evaluate(ctx.clone(), indexable)?332 };333334 for part in parts {335 value = index_val(ctx.clone(), CallLocation::new(&part.span), value, part)?;336 }337 Ok(value)338}339340fn index_val(ctx: Context, loc: CallLocation<'_>, value: Val, part: &LIndexPart) -> Result<Val> {341 let key_val = evaluate(ctx, &part.value)?;342 Ok(match (&value, &key_val) {343 (Val::Obj(obj), Val::Str(key)) => {344 let field = key.clone().into_flat();345 if let Some(v) = obj346 .get(field.clone())347 .with_description_src(loc, || format!("field <{field}> access"))?348 {349 v350 } else {351 bail!(NoSuchField(352 field.clone(),353 suggest_object_fields(obj, field)354 ))355 }356 }357 (Val::Arr(arr), Val::Num(idx)) => {358 let n = idx.get();359 if n.fract() > f64::EPSILON {360 bail!(FractionalIndex)361 }362 if n < 0.0 {363 bail!(ArrayBoundsError(364 n as isize, 365 arr.len()366 ));367 }368 #[expect(369 clippy::cast_possible_truncation,370 clippy::cast_sign_loss,371 reason = "n is checked positive"372 )]373 let i = n as u32;374 arr.get(i)375 .with_description_src(loc, || format!("element <{i}> access"))?376 .ok_or_else(|| ArrayBoundsError(i as isize, arr.len()))?377 }378 (Val::Str(s), Val::Num(idx)) => {379 let n = idx.get();380 if n.fract() > f64::EPSILON {381 bail!(FractionalIndex)382 }383 let flat = s.clone().into_flat();384 if n < 0.0 {385 bail!(ArrayBoundsError(386 n as isize, 387 flat.chars().count() as u32388 ));389 }390 #[expect(391 clippy::cast_possible_truncation,392 clippy::cast_sign_loss,393 reason = "n is checked positive, overflow will truncate as expected"394 )]395 let i = n as usize;396 let Some(char) = flat.chars().nth(i) else {397 bail!(StringBoundsError(i, flat.chars().count()))398 };399 Val::string(char)400 }401 _ => bail!(ValueIndexMustBeTypeGot(402 value.value_type(),403 ValType::Str,404 key_val.value_type()405 )),406 })407}408409fn evaluate_obj_body(super_obj: Option<ObjValue>, ctx: Context, body: &LObjBody) -> Result<Val> {410 match body {411 LObjBody::MemberList(members) => evaluate_obj_members(super_obj, ctx, members),412 LObjBody::ObjComp(comp) => evaluate_obj_comp(super_obj, ctx, comp),413 }414}415416pub fn evaluate_field_member_unbound<B: Unbound<Bound = Context> + Clone>(417 builder: &mut ObjValueBuilder,418 ctx: Context,419 uctx: B,420 field: &LFieldMember,421) -> Result<()> {422 #[derive(Trace)]423 struct UnboundValue<B: Trace> {424 uctx: B,425 value: Rc<LExpr>,426 name: IStr,427 }428 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {429 type Bound = Val;430 fn bind(&self, sup_this: SupThis) -> Result<Val> {431 evaluate(self.uctx.bind(sup_this)?, &self.value)432 }433 }434435 let LFieldMember {436 name,437 plus,438 visibility,439 value,440 } = field;441 let Some(name) = evaluate_field_name(ctx, name)? else {442 return Ok(());443 };444445 builder446 .field(name.clone())447 .with_add(*plus)448 .with_visibility(*visibility)449 .bindable(UnboundValue {450 uctx,451 value: value.clone(),452 name,453 })454}455pub fn evaluate_field_member_static(456 builder: &mut ObjValueBuilder,457 field_ctx: Context,458 value_ctx: Context,459 field: &LFieldMember,460) -> Result<()> {461 let LFieldMember {462 name,463 plus,464 visibility,465 value,466 } = field;467 let Some(name) = evaluate_field_name(field_ctx, name)? else {468 return Ok(());469 };470471 let value = value.clone();472 builder473 .field(name)474 .with_add(*plus)475 .with_visibility(*visibility)476 .try_thunk(Thunk!(move || { evaluate(value_ctx, &value) }))?;477 Ok(())478}479480fn evaluate_obj_members(481 super_obj: Option<ObjValue>,482 ctx: Context,483 members: &LObjMembers,484) -> Result<Val> {485 let mut builder = ObjValueBuilder::with_capacity(members.fields.len());486 if let Some(sup) = super_obj {487 builder.with_super(sup);488 }489490 let needs_unbound = members.this.is_some() || members.uses_super;491492 if needs_unbound {493 let uctx = CachedUnbound::new(evaluate_locals_unbound(494 ctx.clone(),495 members.locals.clone(),496 members.this,497 ));498 for field in &members.fields {499 evaluate_field_member_unbound(&mut builder, ctx.clone(), uctx.clone(), field)?;500 }501 if !members.asserts.is_empty() {502 builder.assert(evaluate_object_assertions_unbound(503 uctx,504 members.asserts.clone(),505 ));506 }507 } else {508 let field_ctx = ctx;509 let value_ctx = evaluate_locals(field_ctx.clone(), &members.locals);510 for field in &members.fields {511 evaluate_field_member_static(512 &mut builder,513 field_ctx.clone(),514 value_ctx.clone(),515 field,516 )?;517 }518 if !members.asserts.is_empty() {519 builder.assert(evaluate_object_assertions_static(520 value_ctx,521 members.asserts.clone(),522 ));523 }524 }525526 Ok(Val::Obj(builder.build()))527}528529pub fn evaluate_assert(ctx: Context, assertion: &LAssertStmt) -> Result<()> {530 let LAssertStmt { cond, message } = assertion;531 let assertion_result = in_frame(532 CallLocation::native(),533 || "assertion condition".to_owned(),534 || bool::from_untyped(evaluate(ctx.clone(), cond)?),535 )?;536 if !assertion_result {537 in_frame(538 CallLocation::new(&cond.span),539 || "assertion failure".to_owned(),540 || {541 if let Some(msg) = message {542 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));543 }544 bail!(AssertionFailed(Val::Null.to_string()?));545 },546 )?;547 }548 Ok(())549}550551fn evaluate_object_assertions_unbound<B: Unbound<Bound = Context>>(552 uctx: B,553 asserts: Rc<Vec<LAssertStmt>>,554) -> impl ObjectAssertion {555 #[derive(Trace)]556 struct ObjectAssert<B: Trace> {557 uctx: B,558 asserts: Rc<Vec<LAssertStmt>>,559 }560 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {561 fn run(&self, sup_this: SupThis) -> Result<()> {562 let ctx = self.uctx.bind(sup_this)?;563 for assert in &*self.asserts {564 evaluate_assert(ctx.clone(), assert)?;565 }566 Ok(())567 }568 }569 ObjectAssert { uctx, asserts }570}571fn evaluate_object_assertions_static(572 ctx: Context,573 asserts: Rc<Vec<LAssertStmt>>,574) -> impl ObjectAssertion {575 #[derive(Trace)]576 struct ObjectAssert {577 ctx: Context,578 asserts: Rc<Vec<LAssertStmt>>,579 }580 impl ObjectAssertion for ObjectAssert {581 fn run(&self, _sup_this: SupThis) -> Result<()> {582 for assert in &*self.asserts {583 evaluate_assert(self.ctx.clone(), assert)?;584 }585 Ok(())586 }587 }588 ObjectAssert { ctx, asserts }589}