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, ObjBody, ObjMembers, ParamsDesc, Spanned,8};9use jrsonnet_types::ValType;10use rustc_hash::FxHashMap;1112use self::destructure::destruct;13use crate::{14 Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, ResultExt, SupThis, Unbound, Val, arr::ArrValue, bail, destructure::evaluate_dest, error::{ErrorKind::*, suggest_object_fields}, evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op}, function::{CallLocation, FuncDesc, FuncVal, builtin::{ParamDefault, ParamName, ParamParse}}, gc::WithCapacityExt as _, in_frame, typed::Typed, val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk}, with_state15};16pub mod destructure;17pub mod operator;1819202122const RED_ZONE: usize = 100 * 1024; 23242526const STACK_PER_RECURSION: usize = 1024 * 1024; 27282930313233#[inline]34pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {35 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)36}3738pub fn evaluate_trivial(expr: &Spanned<Expr>) -> Option<Val> {39 fn is_trivial(expr: &Spanned<Expr>) -> bool {40 match &**expr {41 Expr::Str(_)42 | Expr::Num(_)43 | Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true,44 Expr::Arr(a) => a.iter().all(is_trivial),45 _ => false,46 }47 }48 Some(match &**expr {49 Expr::Str(s) => Val::string(s.clone()),50 Expr::Num(n) => {51 Val::Num(NumValue::new(*n).expect("parser will not allow non-finite values"))52 }53 Expr::Literal(LiteralType::False) => Val::Bool(false),54 Expr::Literal(LiteralType::True) => Val::Bool(true),55 Expr::Literal(LiteralType::Null) => Val::Null,56 Expr::Arr(n) => {57 if n.iter().any(|e| !is_trivial(e)) {58 return None;59 }60 Val::Arr(ArrValue::eager(61 n.iter()62 .map(evaluate_trivial)63 .map(|e| e.expect("checked trivial"))64 .collect(),65 ))66 }67 _ => return None,68 })69}7071pub fn evaluate_method(72 ctx: Context,73 name: IStr,74 params: ParamsDesc,75 body: Rc<Spanned<Expr>>,76) -> Val {77 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {78 name,79 ctx,80 params_parse: params81 .iter()82 .map(|p| {83 ParamParse::new(84 p.0.name().map_or(ParamName::ANONYMOUS, ParamName::new),85 ParamDefault::exists(p.1.is_some()),86 )87 })88 .collect(),89 params,90 body,91 })))92}9394pub fn evaluate_field_name(ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {95 Ok(match field_name {96 FieldName::Fixed(n) => Some(n.clone()),97 FieldName::Dyn(expr) => in_frame(98 CallLocation::new(&expr.span()),99 || "evaluating field name".to_string(),100 || {101 let value = evaluate(ctx, expr)?;102 if matches!(value, Val::Null) {103 Ok(None)104 } else {105 Ok(Some(IStr::from_untyped(value)?))106 }107 },108 )?,109 })110}111112pub fn evaluate_comp(113 ctx: Context,114 specs: &[CompSpec],115 callback: &mut impl FnMut(Context) -> Result<()>,116) -> Result<()> {117 match specs.first() {118 None => callback(ctx)?,119 Some(CompSpec::IfSpec(IfSpecData(cond))) => {120 if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {121 evaluate_comp(ctx, &specs[1..], callback)?;122 }123 }124 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(ctx.clone(), expr)? {125 Val::Arr(list) => {126 for item in list.iter_lazy() {127 let fctx = Pending::new();128 let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());129 destruct(var, item, fctx.clone(), &mut new_bindings)?;130 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);131132 evaluate_comp(ctx, &specs[1..], callback)?;133 }134 }135 #[cfg(feature = "exp-object-iteration")]136 Val::Obj(obj) => {137 for field in obj.fields(138 139 #[cfg(feature = "exp-preserve-order")]140 false,141 ) {142 let fctx = Pending::new();143 let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());144 let obj = obj.clone();145 let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![146 Thunk::evaluated(Val::string(field.clone())),147 Thunk!(move || obj.get(field).transpose().expect(148 "field exists, as field name was obtained from object.fields()",149 )),150 ])));151 destruct(var, value, fctx.clone(), &mut new_bindings)?;152 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);153154 evaluate_comp(ctx, &specs[1..], callback)?;155 }156 }157 _ => bail!(InComprehensionCanOnlyIterateOverArray),158 },159 }160 Ok(())161}162163trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}164impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}165166fn evaluate_object_locals(167 fctx: Context,168 locals: Rc<Vec<BindSpec>>,169) -> impl CloneableUnbound<Context> {170 #[derive(Trace, Clone)]171 struct UnboundLocals {172 fctx: Context,173 locals: Rc<Vec<BindSpec>>,174 }175 impl Unbound for UnboundLocals {176 type Bound = Context;177178 fn bind(&self, sup_this: SupThis) -> Result<Context> {179 let fctx = Context::new_future();180 let mut new_bindings =181 FxHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum());182 for b in self.locals.iter() {183 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;184 }185186 let ctx = self.fctx.clone();187188 let ctx = ctx189 .extend_bindings_sup_this(new_bindings, sup_this)190 .into_future(fctx);191192 Ok(ctx)193 }194 }195196 UnboundLocals { fctx, locals }197}198199pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(200 builder: &mut ObjValueBuilder,201 ctx: Context,202 uctx: B,203 field: &FieldMember,204) -> Result<()> {205 let name = evaluate_field_name(ctx, &field.name)?;206 let Some(name) = name else {207 return Ok(());208 };209210 match field {211 FieldMember {212 plus,213 params: None,214 visibility,215 value,216 ..217 } => {218 #[derive(Trace)]219 struct UnboundValue<B: Trace> {220 uctx: B,221 value: Rc<Spanned<Expr>>,222 name: IStr,223 }224 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {225 type Bound = Val;226 fn bind(&self, sup_this: SupThis) -> Result<Val> {227 evaluate_named(self.uctx.bind(sup_this)?, &self.value, self.name.clone())228 }229 }230231 builder232 .field(name.clone())233 .with_add(*plus)234 .with_visibility(*visibility)235 .with_location(value.span())236 .bindable(UnboundValue {237 uctx,238 value: value.clone(),239 name,240 })?;241 }242 FieldMember {243 params: Some(params),244 visibility,245 value,246 ..247 } => {248 #[derive(Trace)]249 struct UnboundMethod<B: Trace> {250 uctx: B,251 value: Rc<Spanned<Expr>>,252 params: ParamsDesc,253 name: IStr,254 }255 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {256 type Bound = Val;257 fn bind(&self, sup_this: SupThis) -> Result<Val> {258 Ok(evaluate_method(259 self.uctx.bind(sup_this)?,260 self.name.clone(),261 self.params.clone(),262 self.value.clone(),263 ))264 }265 }266267 builder268 .field(name.clone())269 .with_visibility(*visibility)270 .with_location(value.span())271 .bindable(UnboundMethod {272 uctx,273 value: value.clone(),274 params: params.clone(),275 name,276 })?;277 }278 }279 Ok(())280}281282#[allow(clippy::too_many_lines)]283pub fn evaluate_member_list_object(ctx: Context, members: &ObjMembers) -> Result<ObjValue> {284 let mut builder = ObjValueBuilder::new();285 let locals = members.locals.clone();286287 288 let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals));289290 for field in &members.fields {291 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), &field)?;292 }293294 if !members.asserts.is_empty() {295 #[derive(Trace)]296 struct ObjectAssert<B: Trace> {297 uctx: B,298 asserts: Rc<Vec<AssertStmt>>,299 }300 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {301 fn run(&self, sup_this: SupThis) -> Result<()> {302 let ctx = self.uctx.bind(sup_this)?;303 for assert in &*self.asserts {304 evaluate_assert(ctx.clone(), &assert)?;305 }306 Ok(())307 }308 }309 builder.assert(ObjectAssert {310 uctx: uctx.clone(),311 asserts: members.asserts.clone(),312 });313 }314315 Ok(builder.build())316}317318pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {319 Ok(match object {320 ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,321 ObjBody::ObjComp(obj) => {322 let mut builder = ObjValueBuilder::new();323 let locals = obj.locals.clone();324 evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {325 let uctx = evaluate_object_locals(ctx.clone(), locals.clone());326327 evaluate_field_member(&mut builder, ctx, uctx, &obj.field)328 })?;329330 builder.build()331 }332 })333}334335pub fn evaluate_apply(336 ctx: Context,337 value: &Spanned<Expr>,338 args: &ArgsDesc,339 loc: CallLocation<'_>,340 tailstrict: bool,341) -> Result<Val> {342 let value = evaluate(ctx.clone(), value)?;343 Ok(match value {344 Val::Func(f) => {345 let body = || f.evaluate(ctx, loc, args, tailstrict);346 if tailstrict {347 body()?348 } else {349 in_frame(loc, || format!("function <{}> call", f.name()), body)?350 }351 }352 v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())),353 })354}355356pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {357 let value = &assertion.0;358 let msg = &assertion.1;359 let assertion_result = in_frame(360 CallLocation::new(&value.span()),361 || "assertion condition".to_owned(),362 || bool::from_untyped(evaluate(ctx.clone(), value)?),363 )?;364 if !assertion_result {365 in_frame(366 CallLocation::new(&value.span()),367 || "assertion failure".to_owned(),368 || {369 if let Some(msg) = msg {370 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));371 }372 bail!(AssertionFailed(Val::Null.to_string()?));373 },374 )?;375 }376 Ok(())377}378379pub fn evaluate_named(ctx: Context, expr: &Spanned<Expr>, name: IStr) -> Result<Val> {380 use Expr::*;381 Ok(match &**expr {382 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),383 _ => evaluate(ctx, expr)?,384 })385}386387#[allow(clippy::too_many_lines)]388pub fn evaluate(ctx: Context, expr: &Spanned<Expr>) -> Result<Val> {389 use Expr::*;390391 if let Some(trivial) = evaluate_trivial(expr) {392 return Ok(trivial);393 }394 let loc = expr.span();395 Ok(match &**expr {396 Literal(LiteralType::This) => Val::Obj(ctx.try_this()?),397 Literal(LiteralType::Super) => Val::Obj(ctx.try_sup_this()?.standalone_super()?),398 Literal(LiteralType::Dollar) => Val::Obj(ctx.try_dollar()?),399 Literal(LiteralType::True) => Val::Bool(true),400 Literal(LiteralType::False) => Val::Bool(false),401 Literal(LiteralType::Null) => Val::Null,402 Str(v) => Val::string(v.clone()),403 Num(v) => Val::try_num(*v)?,404 405 406 407 408 409 410 BinaryOp(bin)411 if matches!(&*bin.rhs, Expr::Literal(LiteralType::Super))412 && bin.op == BinaryOpType::In =>413 {414 let sup_this = ctx.try_sup_this()?;415 416 417 if !sup_this.has_super() {418 return Ok(Val::Bool(false));419 }420 let field = evaluate(ctx, &bin.lhs)?;421 Val::Bool(sup_this.field_in_super(field.to_string()?))422 }423 BinaryOp(bin) => evaluate_binary_op_special(ctx, &bin.lhs, bin.op, &bin.rhs)?,424 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,425 Var(name) => in_frame(426 CallLocation::new(&loc),427 || format!("local <{name}> access"),428 || ctx.binding(name.clone())?.evaluate(),429 )?,430 Index { indexable, parts } => ensure_sufficient_stack(|| {431 let mut parts = parts.iter();432 let mut indexable = if matches!(&***indexable, Expr::Literal(LiteralType::Super)) {433 let part = parts.next().expect("at least part should exist");434 435 436 let sup_this = ctx.try_sup_this()?;437 if !sup_this.has_super() {438 #[cfg(feature = "exp-null-coaelse")]439 if part.null_coaelse {440 return Ok(Val::Null);441 }442 bail!(NoSuperFound)443 }444 let name = evaluate(ctx.clone(), &part.value)?;445446 let Val::Str(name) = name else {447 bail!(ValueIndexMustBeTypeGot(448 ValType::Obj,449 ValType::Str,450 name.value_type(),451 ))452 };453454 let name = name.into_flat();455 match sup_this456 .get_super(name.clone())457 .with_description_src(&part.value, || format!("field <{name}> access"))?458 {459 Some(v) => v,460 #[cfg(feature = "exp-null-coaelse")]461 None if part.null_coaelse => return Ok(Val::Null),462 None => {463 let suggestions = suggest_object_fields(464 &sup_this.standalone_super().expect("super exists"),465 name.clone(),466 );467468 bail!(NoSuchField(name, suggestions))469 }470 }471 } else {472 evaluate(ctx.clone(), indexable)?473 };474475 for part in parts {476 indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {477 (Val::Obj(v), Val::Str(key)) => match v478 .get(key.clone().into_flat())479 .with_description_src(&part.value, || format!("field <{key}> access"))?480 {481 Some(v) => v,482 #[cfg(feature = "exp-null-coaelse")]483 None if part.null_coaelse => return Ok(Val::Null),484 None => {485 let suggestions = suggest_object_fields(&v, key.clone().into_flat());486487 return Err(Error::from(NoSuchField(488 key.clone().into_flat(),489 suggestions,490 )))491 .with_description_src(&part.value, || format!("field <{key}> access"));492 }493 },494 (Val::Obj(_), n) => bail!(ValueIndexMustBeTypeGot(495 ValType::Obj,496 ValType::Str,497 n.value_type(),498 )),499 (Val::Arr(v), Val::Num(n)) => {500 let n = n.get();501 if n.fract() > f64::EPSILON {502 bail!(FractionalIndex)503 }504 if n < 0.0 {505 bail!(ArrayBoundsError(n as isize, v.len()));506 }507 v.get(n as usize)?508 .ok_or_else(|| ArrayBoundsError(n as isize, v.len()))?509 }510 (Val::Arr(_), Val::Str(n)) => {511 bail!(AttemptedIndexAnArrayWithString(n.into_flat()))512 }513 (Val::Arr(_), n) => bail!(ValueIndexMustBeTypeGot(514 ValType::Arr,515 ValType::Num,516 n.value_type(),517 )),518519 (Val::Str(s), Val::Num(n)) => Val::Str({520 let n = n.get();521 if n.fract() > f64::EPSILON {522 bail!(FractionalIndex)523 }524 if n < 0.0 {525 bail!(ArrayBoundsError(n as isize, s.into_flat().chars().count()));526 }527 let v: IStr = s528 .clone()529 .into_flat()530 .chars()531 .skip(n as usize)532 .take(1)533 .collect::<String>()534 .into();535 if v.is_empty() {536 bail!(StringBoundsError(n as usize, s.into_flat().chars().count()))537 }538 StrValue::Flat(v)539 }),540 (Val::Str(_), n) => bail!(ValueIndexMustBeTypeGot(541 ValType::Str,542 ValType::Num,543 n.value_type(),544 )),545 #[cfg(feature = "exp-null-coaelse")]546 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),547 (v, _) => bail!(CantIndexInto(v.value_type())),548 };549 }550 Ok(indexable)551 })?,552 LocalExpr(bindings, returned) => {553 let mut new_bindings: FxHashMap<IStr, Thunk<Val>> =554 FxHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum());555 let fctx = Context::new_future();556 for b in bindings {557 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;558 }559 let ctx = ctx.extend_bindings(new_bindings).into_future(fctx);560 evaluate(ctx, &returned.clone())?561 }562 Arr(items) => {563 if items.is_empty() {564 Val::Arr(ArrValue::empty())565 } else {566 Val::Arr(ArrValue::expr(ctx, items.clone()))567 }568 }569 ArrComp(expr, comp_specs) => {570 let mut out = Vec::new();571 evaluate_comp(ctx, comp_specs, &mut |ctx| {572 let expr = expr.clone();573 out.push(Thunk!(move || evaluate(ctx, &expr)));574 Ok(())575 })?;576 Val::Arr(ArrValue::lazy(out))577 }578 Obj(body) => Val::Obj(evaluate_object(ctx, body)?),579 ObjExtend(a, b) => evaluate_add_op(580 &evaluate(ctx.clone(), a)?,581 &Val::Obj(evaluate_object(ctx, b)?),582 )?,583 Apply(value, args, tailstrict) => ensure_sufficient_stack(|| {584 evaluate_apply(ctx, value, args, CallLocation::new(&loc), *tailstrict)585 })?,586 Function(params, body) => {587 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())588 }589 AssertExpr(assert) => {590 evaluate_assert(ctx.clone(), &assert.assert)?;591 evaluate(ctx, &assert.rest)?592 }593 ErrorStmt(e) => in_frame(594 CallLocation::new(&loc),595 || "error statement".to_owned(),596 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),597 )?,598 IfElse(if_else) => {599 if in_frame(600 CallLocation::new(&loc),601 || "if condition".to_owned(),602 || bool::from_untyped(evaluate(ctx.clone(), &if_else.cond.0)?),603 )? {604 evaluate(ctx, &if_else.cond_then)?605 } else {606 match &if_else.cond_else {607 Some(v) => evaluate(ctx, v)?,608 None => Val::Null,609 }610 }611 }612 Slice(slice) => {613 fn parse_idx<T: Typed>(614 loc: CallLocation<'_>,615 ctx: Context,616 expr: Option<&Spanned<Expr>>,617 desc: &'static str,618 ) -> Result<Option<T>> {619 if let Some(value) = expr {620 Ok(in_frame(621 loc,622 || format!("slice {desc}"),623 || <Option<T>>::from_untyped(evaluate(ctx, value)?),624 )?)625 } else {626 Ok(None)627 }628 }629630 let indexable = evaluate(ctx.clone(), &slice.value)?;631 let loc = CallLocation::new(&loc);632633 let start = parse_idx(loc, ctx.clone(), slice.slice.start.as_ref(), "start")?;634 let end = parse_idx(loc, ctx.clone(), slice.slice.end.as_ref(), "end")?;635 let step = parse_idx(loc, ctx, slice.slice.step.as_ref(), "step")?;636637 IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?638 }639 Import(kind, path) => {640 let Expr::Str(path) = &***path else {641 bail!("computed imports are not supported")642 };643 let tmp = loc.clone().0;644 with_state(|s| {645 let resolved_path = s.resolve_from(tmp.source_path(), path)?;646 Ok(match kind {647 ImportKind::Normal => in_frame(648 CallLocation::new(&loc),649 || format!("import {:?}", path.clone()),650 || s.import_resolved(resolved_path),651 )?,652 ImportKind::Str => Val::string(s.import_resolved_str(resolved_path)?),653 ImportKind::Bin => {654 Val::Arr(ArrValue::bytes(s.import_resolved_bin(resolved_path)?))655 }656 }) as Result<Val>657 })?658 }659 })660}