1use std::rc::Rc;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_parser::{6 ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, FieldMember, FieldName,7 ForSpecData, IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc,8};9use jrsonnet_types::ValType;10use rustc_hash::FxHashMap;1112use self::destructure::destruct;13use crate::{14 arr::ArrValue,15 bail,16 destructure::evaluate_dest,17 error::{suggest_object_fields, ErrorKind::*},18 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},19 function::{CallLocation, FuncDesc, FuncVal},20 gc::WithCapacityExt as _,21 in_frame,22 typed::Typed,23 val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk},24 with_state, Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,25 ResultExt, SupThis, Unbound, Val,26};27pub mod destructure;28pub mod operator;2930313233const RED_ZONE: usize = 100 * 1024; 34353637const STACK_PER_RECURSION: usize = 1024 * 1024; 38394041424344#[inline]45pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {46 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)47}4849pub fn evaluate_trivial(expr: &LocExpr) -> Option<Val> {50 fn is_trivial(expr: &LocExpr) -> bool {51 match expr.expr() {52 Expr::Str(_)53 | Expr::Num(_)54 | Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true,55 Expr::Arr(a) => a.iter().all(is_trivial),56 _ => false,57 }58 }59 Some(match expr.expr() {60 Expr::Str(s) => Val::string(s.clone()),61 Expr::Num(n) => {62 Val::Num(NumValue::new(*n).expect("parser will not allow non-finite values"))63 }64 Expr::Literal(LiteralType::False) => Val::Bool(false),65 Expr::Literal(LiteralType::True) => Val::Bool(true),66 Expr::Literal(LiteralType::Null) => Val::Null,67 Expr::Arr(n) => {68 if n.iter().any(|e| !is_trivial(e)) {69 return None;70 }71 Val::Arr(ArrValue::eager(72 n.iter()73 .map(evaluate_trivial)74 .map(|e| e.expect("checked trivial"))75 .collect(),76 ))77 }78 _ => return None,79 })80}8182pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {83 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {84 name,85 ctx,86 params,87 body,88 })))89}9091pub fn evaluate_field_name(ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {92 Ok(match field_name {93 FieldName::Fixed(n) => Some(n.clone()),94 FieldName::Dyn(expr) => in_frame(95 CallLocation::new(&expr.span()),96 || "evaluating field name".to_string(),97 || {98 let value = evaluate(ctx, expr)?;99 if matches!(value, Val::Null) {100 Ok(None)101 } else {102 Ok(Some(IStr::from_untyped(value)?))103 }104 },105 )?,106 })107}108109pub fn evaluate_comp(110 ctx: Context,111 specs: &[CompSpec],112 callback: &mut impl FnMut(Context) -> Result<()>,113) -> Result<()> {114 match specs.first() {115 None => callback(ctx)?,116 Some(CompSpec::IfSpec(IfSpecData(cond))) => {117 if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {118 evaluate_comp(ctx, &specs[1..], callback)?;119 }120 }121 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(ctx.clone(), expr)? {122 Val::Arr(list) => {123 for item in list.iter_lazy() {124 let fctx = Pending::new();125 let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());126 destruct(var, item, fctx.clone(), &mut new_bindings)?;127 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);128129 evaluate_comp(ctx, &specs[1..], callback)?;130 }131 }132 #[cfg(feature = "exp-object-iteration")]133 Val::Obj(obj) => {134 for field in obj.fields(135 136 #[cfg(feature = "exp-preserve-order")]137 false,138 ) {139 let fctx = Pending::new();140 let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());141 let obj = obj.clone();142 let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![143 Thunk::evaluated(Val::string(field.clone())),144 Thunk!(move || obj.get(field).transpose().expect(145 "field exists, as field name was obtained from object.fields()",146 )),147 ])));148 destruct(var, value, fctx.clone(), &mut new_bindings)?;149 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);150151 evaluate_comp(ctx, &specs[1..], callback)?;152 }153 }154 _ => bail!(InComprehensionCanOnlyIterateOverArray),155 },156 }157 Ok(())158}159160trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}161impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}162163fn evaluate_object_locals(164 fctx: Context,165 locals: Rc<Vec<BindSpec>>,166) -> impl CloneableUnbound<Context> {167 #[derive(Trace, Clone)]168 struct UnboundLocals {169 fctx: Context,170 locals: Rc<Vec<BindSpec>>,171 }172 impl Unbound for UnboundLocals {173 type Bound = Context;174175 fn bind(&self, sup_this: SupThis) -> Result<Context> {176 let fctx = Context::new_future();177 let mut new_bindings =178 FxHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum());179 for b in self.locals.iter() {180 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;181 }182183 let ctx = self.fctx.clone();184185 let ctx = ctx186 .extend_bindings_sup_this(new_bindings, sup_this)187 .into_future(fctx);188189 Ok(ctx)190 }191 }192193 UnboundLocals { fctx, locals }194}195196pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(197 builder: &mut ObjValueBuilder,198 ctx: Context,199 uctx: B,200 field: &FieldMember,201) -> Result<()> {202 let name = evaluate_field_name(ctx, &field.name)?;203 let Some(name) = name else {204 return Ok(());205 };206207 match field {208 FieldMember {209 plus,210 params: None,211 visibility,212 value,213 ..214 } => {215 #[derive(Trace)]216 struct UnboundValue<B: Trace> {217 uctx: B,218 value: LocExpr,219 name: IStr,220 }221 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {222 type Bound = Val;223 fn bind(&self, sup_this: SupThis) -> Result<Val> {224 evaluate_named(self.uctx.bind(sup_this)?, &self.value, self.name.clone())225 }226 }227228 builder229 .field(name.clone())230 .with_add(*plus)231 .with_visibility(*visibility)232 .with_location(value.span())233 .bindable(UnboundValue {234 uctx,235 value: value.clone(),236 name,237 })?;238 }239 FieldMember {240 params: Some(params),241 visibility,242 value,243 ..244 } => {245 #[derive(Trace)]246 struct UnboundMethod<B: Trace> {247 uctx: B,248 value: LocExpr,249 params: ParamsDesc,250 name: IStr,251 }252 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {253 type Bound = Val;254 fn bind(&self, sup_this: SupThis) -> Result<Val> {255 Ok(evaluate_method(256 self.uctx.bind(sup_this)?,257 self.name.clone(),258 self.params.clone(),259 self.value.clone(),260 ))261 }262 }263264 builder265 .field(name.clone())266 .with_visibility(*visibility)267 .with_location(value.span())268 .bindable(UnboundMethod {269 uctx,270 value: value.clone(),271 params: params.clone(),272 name,273 })?;274 }275 }276 Ok(())277}278279#[allow(clippy::too_many_lines)]280pub fn evaluate_member_list_object(ctx: Context, members: &[Member]) -> Result<ObjValue> {281 let mut builder = ObjValueBuilder::new();282 let locals = Rc::new(283 members284 .iter()285 .filter_map(|m| match m {286 Member::BindStmt(bind) => Some(bind.clone()),287 _ => None,288 })289 .collect::<Vec<_>>(),290 );291292 293 let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals));294295 for member in members {296 match member {297 Member::Field(field) => {298 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;299 }300 Member::AssertStmt(stmt) => {301 #[derive(Trace)]302 struct ObjectAssert<B: Trace> {303 uctx: B,304 assert: AssertStmt,305 }306 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {307 fn run(&self, sup_this: SupThis) -> Result<()> {308 let ctx = self.uctx.bind(sup_this)?;309 evaluate_assert(ctx, &self.assert)310 }311 }312 builder.assert(ObjectAssert {313 uctx: uctx.clone(),314 assert: stmt.clone(),315 });316 }317 Member::BindStmt(_) => {318 319 }320 }321 }322 Ok(builder.build())323}324325pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {326 Ok(match object {327 ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,328 ObjBody::ObjComp(obj) => {329 let mut builder = ObjValueBuilder::new();330 let locals = Rc::new(331 obj.pre_locals332 .iter()333 .chain(obj.post_locals.iter())334 .cloned()335 .collect::<Vec<_>>(),336 );337 evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {338 let uctx = evaluate_object_locals(ctx.clone(), locals.clone());339340 evaluate_field_member(&mut builder, ctx, uctx, &obj.field)341 })?;342343 builder.build()344 }345 })346}347348pub fn evaluate_apply(349 ctx: Context,350 value: &LocExpr,351 args: &ArgsDesc,352 loc: CallLocation<'_>,353 tailstrict: bool,354) -> Result<Val> {355 let value = evaluate(ctx.clone(), value)?;356 Ok(match value {357 Val::Func(f) => {358 let body = || f.evaluate(ctx, loc, args, tailstrict);359 if tailstrict {360 body()?361 } else {362 in_frame(loc, || format!("function <{}> call", f.name()), body)?363 }364 }365 v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())),366 })367}368369pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {370 let value = &assertion.0;371 let msg = &assertion.1;372 let assertion_result = in_frame(373 CallLocation::new(&value.span()),374 || "assertion condition".to_owned(),375 || bool::from_untyped(evaluate(ctx.clone(), value)?),376 )?;377 if !assertion_result {378 in_frame(379 CallLocation::new(&value.span()),380 || "assertion failure".to_owned(),381 || {382 if let Some(msg) = msg {383 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));384 }385 bail!(AssertionFailed(Val::Null.to_string()?));386 },387 )?;388 }389 Ok(())390}391392pub fn evaluate_named(ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {393 use Expr::*;394 Ok(match expr.expr() {395 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),396 _ => evaluate(ctx, expr)?,397 })398}399400#[allow(clippy::too_many_lines)]401pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result<Val> {402 use Expr::*;403404 if let Some(trivial) = evaluate_trivial(expr) {405 return Ok(trivial);406 }407 let loc = expr.span();408 Ok(match expr.expr() {409 Literal(LiteralType::This) => Val::Obj(ctx.try_this()?),410 Literal(LiteralType::Super) => Val::Obj(ctx.try_sup_this()?.standalone_super()?),411 Literal(LiteralType::Dollar) => Val::Obj(ctx.try_dollar()?),412 Literal(LiteralType::True) => Val::Bool(true),413 Literal(LiteralType::False) => Val::Bool(false),414 Literal(LiteralType::Null) => Val::Null,415 Str(v) => Val::string(v.clone()),416 Num(v) => Val::try_num(*v)?,417 418 419 420 421 422 423 BinaryOp(field, BinaryOpType::In, e)424 if matches!(e.expr(), Expr::Literal(LiteralType::Super)) =>425 {426 let sup_this = ctx.try_sup_this()?;427 428 429 if !sup_this.has_super() {430 return Ok(Val::Bool(false));431 }432 let field = evaluate(ctx, field)?;433 Val::Bool(sup_this.field_in_super(field.to_string()?))434 }435 BinaryOp(v1, o, v2) => evaluate_binary_op_special(ctx, v1, *o, v2)?,436 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,437 Var(name) => in_frame(438 CallLocation::new(&loc),439 || format!("local <{name}> access"),440 || ctx.binding(name.clone())?.evaluate(),441 )?,442 Index { indexable, parts } => ensure_sufficient_stack(|| {443 let mut parts = parts.iter();444 let mut indexable = if matches!(indexable.expr(), Expr::Literal(LiteralType::Super)) {445 let part = parts.next().expect("at least part should exist");446 447 448 let sup_this = ctx.try_sup_this()?;449 if !sup_this.has_super() {450 #[cfg(feature = "exp-null-coaelse")]451 if part.null_coaelse {452 return Ok(Val::Null);453 }454 bail!(NoSuperFound)455 }456 let name = evaluate(ctx.clone(), &part.value)?;457458 let Val::Str(name) = name else {459 bail!(ValueIndexMustBeTypeGot(460 ValType::Obj,461 ValType::Str,462 name.value_type(),463 ))464 };465466 let name = name.into_flat();467 match sup_this468 .get_super(name.clone())469 .with_description_src(&part.value, || format!("field <{name}> access"))?470 {471 Some(v) => v,472 #[cfg(feature = "exp-null-coaelse")]473 None if part.null_coaelse => return Ok(Val::Null),474 None => {475 let suggestions = suggest_object_fields(476 &sup_this.standalone_super().expect("super exists"),477 name.clone(),478 );479480 bail!(NoSuchField(name, suggestions))481 }482 }483 } else {484 evaluate(ctx.clone(), indexable)?485 };486487 for part in parts {488 indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {489 (Val::Obj(v), Val::Str(key)) => match v490 .get(key.clone().into_flat())491 .with_description_src(&part.value, || format!("field <{key}> access"))?492 {493 Some(v) => v,494 #[cfg(feature = "exp-null-coaelse")]495 None if part.null_coaelse => return Ok(Val::Null),496 None => {497 let suggestions = suggest_object_fields(&v, key.clone().into_flat());498499 return Err(Error::from(NoSuchField(500 key.clone().into_flat(),501 suggestions,502 )))503 .with_description_src(&part.value, || format!("field <{key}> access"));504 }505 },506 (Val::Obj(_), n) => bail!(ValueIndexMustBeTypeGot(507 ValType::Obj,508 ValType::Str,509 n.value_type(),510 )),511 (Val::Arr(v), Val::Num(n)) => {512 let n = n.get();513 if n.fract() > f64::EPSILON {514 bail!(FractionalIndex)515 }516 if n < 0.0 {517 bail!(ArrayBoundsError(n as isize, v.len()));518 }519 v.get(n as usize)?520 .ok_or_else(|| ArrayBoundsError(n as isize, v.len()))?521 }522 (Val::Arr(_), Val::Str(n)) => {523 bail!(AttemptedIndexAnArrayWithString(n.into_flat()))524 }525 (Val::Arr(_), n) => bail!(ValueIndexMustBeTypeGot(526 ValType::Arr,527 ValType::Num,528 n.value_type(),529 )),530531 (Val::Str(s), Val::Num(n)) => Val::Str({532 let n = n.get();533 if n.fract() > f64::EPSILON {534 bail!(FractionalIndex)535 }536 if n < 0.0 {537 bail!(ArrayBoundsError(n as isize, s.into_flat().chars().count()));538 }539 let v: IStr = s540 .clone()541 .into_flat()542 .chars()543 .skip(n as usize)544 .take(1)545 .collect::<String>()546 .into();547 if v.is_empty() {548 bail!(StringBoundsError(n as usize, s.into_flat().chars().count()))549 }550 StrValue::Flat(v)551 }),552 (Val::Str(_), n) => bail!(ValueIndexMustBeTypeGot(553 ValType::Str,554 ValType::Num,555 n.value_type(),556 )),557 #[cfg(feature = "exp-null-coaelse")]558 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),559 (v, _) => bail!(CantIndexInto(v.value_type())),560 };561 }562 Ok(indexable)563 })?,564 LocalExpr(bindings, returned) => {565 let mut new_bindings: FxHashMap<IStr, Thunk<Val>> =566 FxHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum());567 let fctx = Context::new_future();568 for b in bindings {569 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;570 }571 let ctx = ctx.extend_bindings(new_bindings).into_future(fctx);572 evaluate(ctx, &returned.clone())?573 }574 Arr(items) => {575 if items.is_empty() {576 Val::Arr(ArrValue::empty())577 } else if items.len() == 1 {578 let item = items[0].clone();579 Val::Arr(ArrValue::lazy(vec![Thunk!(move || evaluate(ctx, &item))]))580 } else {581 Val::Arr(ArrValue::expr(ctx, items.iter().cloned()))582 }583 }584 ArrComp(expr, comp_specs) => {585 let mut out = Vec::new();586 evaluate_comp(ctx, comp_specs, &mut |ctx| {587 let expr = expr.clone();588 out.push(Thunk!(move || evaluate(ctx, &expr)));589 Ok(())590 })?;591 Val::Arr(ArrValue::lazy(out))592 }593 Obj(body) => Val::Obj(evaluate_object(ctx, body)?),594 ObjExtend(a, b) => evaluate_add_op(595 &evaluate(ctx.clone(), a)?,596 &Val::Obj(evaluate_object(ctx, b)?),597 )?,598 Apply(value, args, tailstrict) => ensure_sufficient_stack(|| {599 evaluate_apply(ctx, value, args, CallLocation::new(&loc), *tailstrict)600 })?,601 Function(params, body) => {602 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())603 }604 AssertExpr(assert, returned) => {605 evaluate_assert(ctx.clone(), assert)?;606 evaluate(ctx, returned)?607 }608 ErrorStmt(e) => in_frame(609 CallLocation::new(&loc),610 || "error statement".to_owned(),611 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),612 )?,613 IfElse {614 cond,615 cond_then,616 cond_else,617 } => {618 if in_frame(619 CallLocation::new(&loc),620 || "if condition".to_owned(),621 || bool::from_untyped(evaluate(ctx.clone(), &cond.0)?),622 )? {623 evaluate(ctx, cond_then)?624 } else {625 match cond_else {626 Some(v) => evaluate(ctx, v)?,627 None => Val::Null,628 }629 }630 }631 Slice(value, desc) => {632 fn parse_idx<T: Typed>(633 loc: CallLocation<'_>,634 ctx: Context,635 expr: Option<&LocExpr>,636 desc: &'static str,637 ) -> Result<Option<T>> {638 if let Some(value) = expr {639 Ok(in_frame(640 loc,641 || format!("slice {desc}"),642 || <Option<T>>::from_untyped(evaluate(ctx, value)?),643 )?)644 } else {645 Ok(None)646 }647 }648649 let indexable = evaluate(ctx.clone(), value)?;650 let loc = CallLocation::new(&loc);651652 let start = parse_idx(loc, ctx.clone(), desc.start.as_ref(), "start")?;653 let end = parse_idx(loc, ctx.clone(), desc.end.as_ref(), "end")?;654 let step = parse_idx(loc, ctx, desc.step.as_ref(), "step")?;655656 IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?657 }658 i @ (Import(path) | ImportStr(path) | ImportBin(path)) => {659 let Expr::Str(path) = &path.expr() else {660 bail!("computed imports are not supported")661 };662 let tmp = loc.clone().0;663 with_state(|s| {664 let resolved_path = s.resolve_from(tmp.source_path(), path)?;665 Ok(match i {666 Import(_) => in_frame(667 CallLocation::new(&loc),668 || format!("import {:?}", path.clone()),669 || s.import_resolved(resolved_path),670 )?,671 ImportStr(_) => Val::string(s.import_resolved_str(resolved_path)?),672 ImportBin(_) => {673 Val::Arr(ArrValue::bytes(s.import_resolved_bin(resolved_path)?))674 }675 _ => unreachable!(),676 }) as Result<Val>677 })?678 }679 })680}