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, ImportKind, LiteralType, Member, ObjBody, ParamsDesc, Spanned,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: &Spanned<Expr>) -> Option<Val> {50 fn is_trivial(expr: &Spanned<Expr>) -> bool {51 match &**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 {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(83 ctx: Context,84 name: IStr,85 params: ParamsDesc,86 body: Rc<Spanned<Expr>>,87) -> Val {88 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {89 name,90 ctx,91 params,92 body,93 })))94}9596pub fn evaluate_field_name(ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {97 Ok(match field_name {98 FieldName::Fixed(n) => Some(n.clone()),99 FieldName::Dyn(expr) => in_frame(100 CallLocation::new(&expr.span()),101 || "evaluating field name".to_string(),102 || {103 let value = evaluate(ctx, expr)?;104 if matches!(value, Val::Null) {105 Ok(None)106 } else {107 Ok(Some(IStr::from_untyped(value)?))108 }109 },110 )?,111 })112}113114pub fn evaluate_comp(115 ctx: Context,116 specs: &[CompSpec],117 callback: &mut impl FnMut(Context) -> Result<()>,118) -> Result<()> {119 match specs.first() {120 None => callback(ctx)?,121 Some(CompSpec::IfSpec(IfSpecData(cond))) => {122 if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {123 evaluate_comp(ctx, &specs[1..], callback)?;124 }125 }126 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(ctx.clone(), expr)? {127 Val::Arr(list) => {128 for item in list.iter_lazy() {129 let fctx = Pending::new();130 let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());131 destruct(var, item, fctx.clone(), &mut new_bindings)?;132 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);133134 evaluate_comp(ctx, &specs[1..], callback)?;135 }136 }137 #[cfg(feature = "exp-object-iteration")]138 Val::Obj(obj) => {139 for field in obj.fields(140 141 #[cfg(feature = "exp-preserve-order")]142 false,143 ) {144 let fctx = Pending::new();145 let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());146 let obj = obj.clone();147 let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![148 Thunk::evaluated(Val::string(field.clone())),149 Thunk!(move || obj.get(field).transpose().expect(150 "field exists, as field name was obtained from object.fields()",151 )),152 ])));153 destruct(var, value, fctx.clone(), &mut new_bindings)?;154 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);155156 evaluate_comp(ctx, &specs[1..], callback)?;157 }158 }159 _ => bail!(InComprehensionCanOnlyIterateOverArray),160 },161 }162 Ok(())163}164165trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}166impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}167168fn evaluate_object_locals(169 fctx: Context,170 locals: Rc<Vec<BindSpec>>,171) -> impl CloneableUnbound<Context> {172 #[derive(Trace, Clone)]173 struct UnboundLocals {174 fctx: Context,175 locals: Rc<Vec<BindSpec>>,176 }177 impl Unbound for UnboundLocals {178 type Bound = Context;179180 fn bind(&self, sup_this: SupThis) -> Result<Context> {181 let fctx = Context::new_future();182 let mut new_bindings =183 FxHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum());184 for b in self.locals.iter() {185 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;186 }187188 let ctx = self.fctx.clone();189190 let ctx = ctx191 .extend_bindings_sup_this(new_bindings, sup_this)192 .into_future(fctx);193194 Ok(ctx)195 }196 }197198 UnboundLocals { fctx, locals }199}200201pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(202 builder: &mut ObjValueBuilder,203 ctx: Context,204 uctx: B,205 field: &FieldMember,206) -> Result<()> {207 let name = evaluate_field_name(ctx, &field.name)?;208 let Some(name) = name else {209 return Ok(());210 };211212 match field {213 FieldMember {214 plus,215 params: None,216 visibility,217 value,218 ..219 } => {220 #[derive(Trace)]221 struct UnboundValue<B: Trace> {222 uctx: B,223 value: Rc<Spanned<Expr>>,224 name: IStr,225 }226 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {227 type Bound = Val;228 fn bind(&self, sup_this: SupThis) -> Result<Val> {229 evaluate_named(self.uctx.bind(sup_this)?, &self.value, self.name.clone())230 }231 }232233 builder234 .field(name.clone())235 .with_add(*plus)236 .with_visibility(*visibility)237 .with_location(value.span())238 .bindable(UnboundValue {239 uctx,240 value: value.clone(),241 name,242 })?;243 }244 FieldMember {245 params: Some(params),246 visibility,247 value,248 ..249 } => {250 #[derive(Trace)]251 struct UnboundMethod<B: Trace> {252 uctx: B,253 value: Rc<Spanned<Expr>>,254 params: ParamsDesc,255 name: IStr,256 }257 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {258 type Bound = Val;259 fn bind(&self, sup_this: SupThis) -> Result<Val> {260 Ok(evaluate_method(261 self.uctx.bind(sup_this)?,262 self.name.clone(),263 self.params.clone(),264 self.value.clone(),265 ))266 }267 }268269 builder270 .field(name.clone())271 .with_visibility(*visibility)272 .with_location(value.span())273 .bindable(UnboundMethod {274 uctx,275 value: value.clone(),276 params: params.clone(),277 name,278 })?;279 }280 }281 Ok(())282}283284#[allow(clippy::too_many_lines)]285pub fn evaluate_member_list_object(ctx: Context, members: &[Member]) -> Result<ObjValue> {286 let mut builder = ObjValueBuilder::new();287 let locals = Rc::new(288 members289 .iter()290 .filter_map(|m| match m {291 Member::BindStmt(bind) => Some(bind.clone()),292 _ => None,293 })294 .collect::<Vec<_>>(),295 );296297 298 let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals));299300 for member in members {301 match member {302 Member::Field(field) => {303 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;304 }305 Member::AssertStmt(stmt) => {306 #[derive(Trace)]307 struct ObjectAssert<B: Trace> {308 uctx: B,309 assert: Rc<AssertStmt>,310 }311 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {312 fn run(&self, sup_this: SupThis) -> Result<()> {313 let ctx = self.uctx.bind(sup_this)?;314 evaluate_assert(ctx, &self.assert)315 }316 }317 builder.assert(ObjectAssert {318 uctx: uctx.clone(),319 assert: stmt.clone(),320 });321 }322 Member::BindStmt(_) => {323 324 }325 }326 }327 Ok(builder.build())328}329330pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {331 Ok(match object {332 ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,333 ObjBody::ObjComp(obj) => {334 let mut builder = ObjValueBuilder::new();335 let locals = Rc::new(336 obj.pre_locals337 .iter()338 .chain(obj.post_locals.iter())339 .cloned()340 .collect::<Vec<_>>(),341 );342 evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {343 let uctx = evaluate_object_locals(ctx.clone(), locals.clone());344345 evaluate_field_member(&mut builder, ctx, uctx, &obj.field)346 })?;347348 builder.build()349 }350 })351}352353pub fn evaluate_apply(354 ctx: Context,355 value: &Spanned<Expr>,356 args: &ArgsDesc,357 loc: CallLocation<'_>,358 tailstrict: bool,359) -> Result<Val> {360 let value = evaluate(ctx.clone(), value)?;361 Ok(match value {362 Val::Func(f) => {363 let body = || f.evaluate(ctx, loc, args, tailstrict);364 if tailstrict {365 body()?366 } else {367 in_frame(loc, || format!("function <{}> call", f.name()), body)?368 }369 }370 v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())),371 })372}373374pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {375 let value = &assertion.0;376 let msg = &assertion.1;377 let assertion_result = in_frame(378 CallLocation::new(&value.span()),379 || "assertion condition".to_owned(),380 || bool::from_untyped(evaluate(ctx.clone(), value)?),381 )?;382 if !assertion_result {383 in_frame(384 CallLocation::new(&value.span()),385 || "assertion failure".to_owned(),386 || {387 if let Some(msg) = msg {388 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));389 }390 bail!(AssertionFailed(Val::Null.to_string()?));391 },392 )?;393 }394 Ok(())395}396397pub fn evaluate_named(ctx: Context, expr: &Spanned<Expr>, name: IStr) -> Result<Val> {398 use Expr::*;399 Ok(match &**expr {400 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),401 _ => evaluate(ctx, expr)?,402 })403}404405#[allow(clippy::too_many_lines)]406pub fn evaluate(ctx: Context, expr: &Spanned<Expr>) -> Result<Val> {407 use Expr::*;408409 if let Some(trivial) = evaluate_trivial(expr) {410 return Ok(trivial);411 }412 let loc = expr.span();413 Ok(match &**expr {414 Literal(LiteralType::This) => Val::Obj(ctx.try_this()?),415 Literal(LiteralType::Super) => Val::Obj(ctx.try_sup_this()?.standalone_super()?),416 Literal(LiteralType::Dollar) => Val::Obj(ctx.try_dollar()?),417 Literal(LiteralType::True) => Val::Bool(true),418 Literal(LiteralType::False) => Val::Bool(false),419 Literal(LiteralType::Null) => Val::Null,420 Str(v) => Val::string(v.clone()),421 Num(v) => Val::try_num(*v)?,422 423 424 425 426 427 428 BinaryOp(bin)429 if matches!(&*bin.rhs, Expr::Literal(LiteralType::Super))430 && bin.op == BinaryOpType::In =>431 {432 let sup_this = ctx.try_sup_this()?;433 434 435 if !sup_this.has_super() {436 return Ok(Val::Bool(false));437 }438 let field = evaluate(ctx, &bin.lhs)?;439 Val::Bool(sup_this.field_in_super(field.to_string()?))440 }441 BinaryOp(bin) => evaluate_binary_op_special(ctx, &bin.lhs, bin.op, &bin.rhs)?,442 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,443 Var(name) => in_frame(444 CallLocation::new(&loc),445 || format!("local <{name}> access"),446 || ctx.binding(name.clone())?.evaluate(),447 )?,448 Index { indexable, parts } => ensure_sufficient_stack(|| {449 let mut parts = parts.iter();450 let mut indexable = if matches!(&***indexable, Expr::Literal(LiteralType::Super)) {451 let part = parts.next().expect("at least part should exist");452 453 454 let sup_this = ctx.try_sup_this()?;455 if !sup_this.has_super() {456 #[cfg(feature = "exp-null-coaelse")]457 if part.null_coaelse {458 return Ok(Val::Null);459 }460 bail!(NoSuperFound)461 }462 let name = evaluate(ctx.clone(), &part.value)?;463464 let Val::Str(name) = name else {465 bail!(ValueIndexMustBeTypeGot(466 ValType::Obj,467 ValType::Str,468 name.value_type(),469 ))470 };471472 let name = name.into_flat();473 match sup_this474 .get_super(name.clone())475 .with_description_src(&part.value, || format!("field <{name}> access"))?476 {477 Some(v) => v,478 #[cfg(feature = "exp-null-coaelse")]479 None if part.null_coaelse => return Ok(Val::Null),480 None => {481 let suggestions = suggest_object_fields(482 &sup_this.standalone_super().expect("super exists"),483 name.clone(),484 );485486 bail!(NoSuchField(name, suggestions))487 }488 }489 } else {490 evaluate(ctx.clone(), indexable)?491 };492493 for part in parts {494 indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {495 (Val::Obj(v), Val::Str(key)) => match v496 .get(key.clone().into_flat())497 .with_description_src(&part.value, || format!("field <{key}> access"))?498 {499 Some(v) => v,500 #[cfg(feature = "exp-null-coaelse")]501 None if part.null_coaelse => return Ok(Val::Null),502 None => {503 let suggestions = suggest_object_fields(&v, key.clone().into_flat());504505 return Err(Error::from(NoSuchField(506 key.clone().into_flat(),507 suggestions,508 )))509 .with_description_src(&part.value, || format!("field <{key}> access"));510 }511 },512 (Val::Obj(_), n) => bail!(ValueIndexMustBeTypeGot(513 ValType::Obj,514 ValType::Str,515 n.value_type(),516 )),517 (Val::Arr(v), Val::Num(n)) => {518 let n = n.get();519 if n.fract() > f64::EPSILON {520 bail!(FractionalIndex)521 }522 if n < 0.0 {523 bail!(ArrayBoundsError(n as isize, v.len()));524 }525 v.get(n as usize)?526 .ok_or_else(|| ArrayBoundsError(n as isize, v.len()))?527 }528 (Val::Arr(_), Val::Str(n)) => {529 bail!(AttemptedIndexAnArrayWithString(n.into_flat()))530 }531 (Val::Arr(_), n) => bail!(ValueIndexMustBeTypeGot(532 ValType::Arr,533 ValType::Num,534 n.value_type(),535 )),536537 (Val::Str(s), Val::Num(n)) => Val::Str({538 let n = n.get();539 if n.fract() > f64::EPSILON {540 bail!(FractionalIndex)541 }542 if n < 0.0 {543 bail!(ArrayBoundsError(n as isize, s.into_flat().chars().count()));544 }545 let v: IStr = s546 .clone()547 .into_flat()548 .chars()549 .skip(n as usize)550 .take(1)551 .collect::<String>()552 .into();553 if v.is_empty() {554 bail!(StringBoundsError(n as usize, s.into_flat().chars().count()))555 }556 StrValue::Flat(v)557 }),558 (Val::Str(_), n) => bail!(ValueIndexMustBeTypeGot(559 ValType::Str,560 ValType::Num,561 n.value_type(),562 )),563 #[cfg(feature = "exp-null-coaelse")]564 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),565 (v, _) => bail!(CantIndexInto(v.value_type())),566 };567 }568 Ok(indexable)569 })?,570 LocalExpr(bindings, returned) => {571 let mut new_bindings: FxHashMap<IStr, Thunk<Val>> =572 FxHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum());573 let fctx = Context::new_future();574 for b in bindings {575 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;576 }577 let ctx = ctx.extend_bindings(new_bindings).into_future(fctx);578 evaluate(ctx, &returned.clone())?579 }580 Arr(items) => {581 if items.is_empty() {582 Val::Arr(ArrValue::empty())583 } else {584 Val::Arr(ArrValue::expr(ctx, items.clone()))585 }586 }587 ArrComp(expr, comp_specs) => {588 let mut out = Vec::new();589 evaluate_comp(ctx, comp_specs, &mut |ctx| {590 let expr = expr.clone();591 out.push(Thunk!(move || evaluate(ctx, &expr)));592 Ok(())593 })?;594 Val::Arr(ArrValue::lazy(out))595 }596 Obj(body) => Val::Obj(evaluate_object(ctx, body)?),597 ObjExtend(a, b) => evaluate_add_op(598 &evaluate(ctx.clone(), a)?,599 &Val::Obj(evaluate_object(ctx, b)?),600 )?,601 Apply(value, args, tailstrict) => ensure_sufficient_stack(|| {602 evaluate_apply(ctx, value, args, CallLocation::new(&loc), *tailstrict)603 })?,604 Function(params, body) => {605 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())606 }607 AssertExpr(assert) => {608 evaluate_assert(ctx.clone(), &assert.assert)?;609 evaluate(ctx, &assert.rest)?610 }611 ErrorStmt(e) => in_frame(612 CallLocation::new(&loc),613 || "error statement".to_owned(),614 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),615 )?,616 IfElse (if_else)617 618 619 620 621 622 => {623 if in_frame(624 CallLocation::new(&loc),625 || "if condition".to_owned(),626 || bool::from_untyped(evaluate(ctx.clone(), &if_else.cond.0)?),627 )? {628 evaluate(ctx, &if_else.cond_then)?629 } else {630 match &if_else.cond_else {631 Some(v) => evaluate(ctx, v)?,632 None => Val::Null,633 }634 }635 }636 Slice(slice) => {637 fn parse_idx<T: Typed>(638 loc: CallLocation<'_>,639 ctx: Context,640 expr: Option<&Spanned<Expr>>,641 desc: &'static str,642 ) -> Result<Option<T>> {643 if let Some(value) = expr {644 Ok(in_frame(645 loc,646 || format!("slice {desc}"),647 || <Option<T>>::from_untyped(evaluate(ctx, value)?),648 )?)649 } else {650 Ok(None)651 }652 }653654 let indexable = evaluate(ctx.clone(), &slice.value)?;655 let loc = CallLocation::new(&loc);656657 let start = parse_idx(loc, ctx.clone(), slice.slice.start.as_ref(), "start")?;658 let end = parse_idx(loc, ctx.clone(), slice.slice.end.as_ref(), "end")?;659 let step = parse_idx(loc, ctx, slice.slice.step.as_ref(), "step")?;660661 IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?662 }663 Import(kind, path) => {664 let Expr::Str(path) = &***path else {665 bail!("computed imports are not supported")666 };667 let tmp = loc.clone().0;668 with_state(|s| {669 let resolved_path = s.resolve_from(tmp.source_path(), path)?;670 Ok(match kind {671 ImportKind::Normal => in_frame(672 CallLocation::new(&loc),673 || format!("import {:?}", path.clone()),674 || s.import_resolved(resolved_path),675 )?,676 ImportKind::Str => Val::string(s.import_resolved_str(resolved_path)?),677 ImportKind::Bin => {678 Val::Arr(ArrValue::bytes(s.import_resolved_bin(resolved_path)?))679 }680 }) as Result<Val>681 })?682 }683 })684}