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;1011use self::destructure::destruct;12use crate::{13 arr::ArrValue,14 bail,15 destructure::evaluate_dest,16 error::{suggest_object_fields, ErrorKind::*},17 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},18 function::{CallLocation, FuncDesc, FuncVal},19 in_frame,20 typed::Typed,21 val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk},22 Context, Error, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,23 ResultExt, Unbound, Val,24};25pub mod destructure;26pub mod operator;2728293031const RED_ZONE: usize = 100 * 1024; 32333435const STACK_PER_RECURSION: usize = 1024 * 1024; 36373839404142#[inline]43pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {44 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)45}4647pub fn evaluate_trivial(expr: &LocExpr) -> Option<Val> {48 fn is_trivial(expr: &LocExpr) -> bool {49 match expr.expr() {50 Expr::Str(_)51 | Expr::Num(_)52 | Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true,53 Expr::Arr(a) => a.iter().all(is_trivial),54 Expr::Parened(e) => is_trivial(e),55 _ => false,56 }57 }58 Some(match expr.expr() {59 Expr::Str(s) => Val::string(s.clone()),60 Expr::Num(n) => {61 Val::Num(NumValue::new(*n).expect("parser will not allow non-finite values"))62 }63 Expr::Literal(LiteralType::False) => Val::Bool(false),64 Expr::Literal(LiteralType::True) => Val::Bool(true),65 Expr::Literal(LiteralType::Null) => Val::Null,66 Expr::Arr(n) => {67 if n.iter().any(|e| !is_trivial(e)) {68 return None;69 }70 Val::Arr(ArrValue::eager(71 n.iter()72 .map(evaluate_trivial)73 .map(|e| e.expect("checked trivial"))74 .collect(),75 ))76 }77 Expr::Parened(e) => evaluate_trivial(e)?,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 = GcHashMap::with_capacity(var.capacity_hint());126 destruct(var, item, fctx.clone(), &mut new_bindings)?;127 let ctx = ctx128 .clone()129 .extend(new_bindings, None, None, None)130 .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 = GcHashMap::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 = ctx153 .clone()154 .extend(new_bindings, None, None, None)155 .into_future(fctx);156157 evaluate_comp(ctx, &specs[1..], callback)?;158 }159 }160 _ => bail!(InComprehensionCanOnlyIterateOverArray),161 },162 }163 Ok(())164}165166trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}167impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}168169fn evaluate_object_locals(170 fctx: Pending<Context>,171 locals: Rc<Vec<BindSpec>>,172) -> impl CloneableUnbound<Context> {173 #[derive(Trace, Clone)]174 struct UnboundLocals {175 fctx: Pending<Context>,176 locals: Rc<Vec<BindSpec>>,177 }178 impl Unbound for UnboundLocals {179 type Bound = Context;180181 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Context> {182 let fctx = Context::new_future();183 let mut new_bindings =184 GcHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum());185 for b in self.locals.iter() {186 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;187 }188189 let ctx = self.fctx.unwrap();190 let new_dollar = ctx.dollar().cloned().or_else(|| this.clone());191192 let ctx = ctx193 .extend(new_bindings, new_dollar, sup, this)194 .into_future(fctx);195196 Ok(ctx)197 }198 }199200 UnboundLocals { fctx, locals }201}202203pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(204 builder: &mut ObjValueBuilder,205 ctx: Context,206 uctx: B,207 field: &FieldMember,208) -> Result<()> {209 let name = evaluate_field_name(ctx, &field.name)?;210 let Some(name) = name else {211 return Ok(());212 };213214 match field {215 FieldMember {216 plus,217 params: None,218 visibility,219 value,220 ..221 } => {222 #[derive(Trace)]223 struct UnboundValue<B: Trace> {224 uctx: B,225 value: LocExpr,226 name: IStr,227 }228 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {229 type Bound = Val;230 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {231 evaluate_named(self.uctx.bind(sup, this)?, &self.value, self.name.clone())232 }233 }234235 builder236 .field(name.clone())237 .with_add(*plus)238 .with_visibility(*visibility)239 .with_location(value.span())240 .bindable(UnboundValue {241 uctx,242 value: value.clone(),243 name,244 })?;245 }246 FieldMember {247 params: Some(params),248 visibility,249 value,250 ..251 } => {252 #[derive(Trace)]253 struct UnboundMethod<B: Trace> {254 uctx: B,255 value: LocExpr,256 params: ParamsDesc,257 name: IStr,258 }259 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {260 type Bound = Val;261 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {262 Ok(evaluate_method(263 self.uctx.bind(sup, this)?,264 self.name.clone(),265 self.params.clone(),266 self.value.clone(),267 ))268 }269 }270271 builder272 .field(name.clone())273 .with_visibility(*visibility)274 .with_location(value.span())275 .bindable(UnboundMethod {276 uctx,277 value: value.clone(),278 params: params.clone(),279 name,280 })?;281 }282 }283 Ok(())284}285286#[allow(clippy::too_many_lines)]287pub fn evaluate_member_list_object(ctx: Context, members: &[Member]) -> Result<ObjValue> {288 let mut builder = ObjValueBuilder::new();289 let locals = Rc::new(290 members291 .iter()292 .filter_map(|m| match m {293 Member::BindStmt(bind) => Some(bind.clone()),294 _ => None,295 })296 .collect::<Vec<_>>(),297 );298299 let fctx = Context::new_future();300301 302 let uctx = CachedUnbound::new(evaluate_object_locals(fctx.clone(), locals));303304 for member in members {305 match member {306 Member::Field(field) => {307 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;308 }309 Member::AssertStmt(stmt) => {310 #[derive(Trace)]311 struct ObjectAssert<B: Trace> {312 uctx: B,313 assert: AssertStmt,314 }315 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {316 fn run(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<()> {317 let ctx = self.uctx.bind(sup, this)?;318 evaluate_assert(ctx, &self.assert)319 }320 }321 builder.assert(ObjectAssert {322 uctx: uctx.clone(),323 assert: stmt.clone(),324 });325 }326 Member::BindStmt(_) => {327 328 }329 }330 }331 let this = builder.build();332 fctx.fill(ctx.extend(GcHashMap::new(), None, None, Some(this.clone())));333 Ok(this)334}335336pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {337 Ok(match object {338 ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,339 ObjBody::ObjComp(obj) => {340 let mut builder = ObjValueBuilder::new();341 let locals = Rc::new(342 obj.pre_locals343 .iter()344 .chain(obj.post_locals.iter())345 .cloned()346 .collect::<Vec<_>>(),347 );348 let mut ctxs = vec![];349 evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {350 let fctx = Context::new_future();351 ctxs.push((ctx.clone(), fctx.clone()));352 let uctx = evaluate_object_locals(fctx, locals.clone());353354 evaluate_field_member(&mut builder, ctx, uctx, &obj.field)355 })?;356357 let this = builder.build();358 for (ctx, fctx) in ctxs {359 let _ctx = ctx360 .extend(GcHashMap::new(), None, None, Some(this.clone()))361 .into_future(fctx);362 }363 this364 }365 })366}367368pub fn evaluate_apply(369 ctx: Context,370 value: &LocExpr,371 args: &ArgsDesc,372 loc: CallLocation<'_>,373 tailstrict: bool,374) -> Result<Val> {375 let value = evaluate(ctx.clone(), value)?;376 Ok(match value {377 Val::Func(f) => {378 let body = || f.evaluate(ctx, loc, args, tailstrict);379 if tailstrict {380 body()?381 } else {382 in_frame(loc, || format!("function <{}> call", f.name()), body)?383 }384 }385 v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())),386 })387}388389pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {390 let value = &assertion.0;391 let msg = &assertion.1;392 let assertion_result = in_frame(393 CallLocation::new(&value.span()),394 || "assertion condition".to_owned(),395 || bool::from_untyped(evaluate(ctx.clone(), value)?),396 )?;397 if !assertion_result {398 in_frame(399 CallLocation::new(&value.span()),400 || "assertion failure".to_owned(),401 || {402 if let Some(msg) = msg {403 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));404 }405 bail!(AssertionFailed(Val::Null.to_string()?));406 },407 )?;408 }409 Ok(())410}411412pub fn evaluate_named(ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {413 use Expr::*;414 Ok(match expr.expr() {415 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),416 _ => evaluate(ctx, expr)?,417 })418}419420#[allow(clippy::too_many_lines)]421pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result<Val> {422 use Expr::*;423424 if let Some(trivial) = evaluate_trivial(expr) {425 return Ok(trivial);426 }427 let loc = expr.span();428 Ok(match expr.expr() {429 Literal(LiteralType::This) => {430 Val::Obj(ctx.this().ok_or(CantUseSelfOutsideOfObject)?.clone())431 }432 Literal(LiteralType::Super) => Val::Obj(433 ctx.super_obj().ok_or(NoSuperFound)?.with_this(434 ctx.this()435 .expect("if super exists - then this should too")436 .clone(),437 ),438 ),439 Literal(LiteralType::Dollar) => {440 Val::Obj(ctx.dollar().ok_or(NoTopLevelObjectFound)?.clone())441 }442 Literal(LiteralType::True) => Val::Bool(true),443 Literal(LiteralType::False) => Val::Bool(false),444 Literal(LiteralType::Null) => Val::Null,445 Parened(e) => evaluate(ctx, e)?,446 Str(v) => Val::string(v.clone()),447 Num(v) => Val::try_num(*v)?,448 449 450 451 452 453 454 BinaryOp(field, BinaryOpType::In, e)455 if matches!(e.expr(), Expr::Literal(LiteralType::Super)) =>456 {457 let Some(super_obj) = ctx.super_obj() else {458 return Ok(Val::Bool(false));459 };460 let field = evaluate(ctx.clone(), field)?;461 Val::Bool(super_obj.has_field_ex(field.to_string()?, true))462 }463 BinaryOp(v1, o, v2) => evaluate_binary_op_special(ctx, v1, *o, v2)?,464 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,465 Var(name) => in_frame(466 CallLocation::new(&loc),467 || format!("local <{name}> access"),468 || ctx.binding(name.clone())?.evaluate(),469 )?,470 Index { indexable, parts } => ensure_sufficient_stack(|| {471 let mut parts = parts.iter();472 let mut indexable = if matches!(indexable.expr(), Expr::Literal(LiteralType::Super)) {473 let part = parts.next().expect("at least part should exist");474 let Some(super_obj) = ctx.super_obj() else {475 #[cfg(feature = "exp-null-coaelse")]476 if part.null_coaelse {477 return Ok(Val::Null);478 }479 bail!(NoSuperFound)480 };481 let name = evaluate(ctx.clone(), &part.value)?;482483 let Val::Str(name) = name else {484 bail!(ValueIndexMustBeTypeGot(485 ValType::Obj,486 ValType::Str,487 name.value_type(),488 ))489 };490491 let this = ctx492 .this()493 .expect("no this found, while super present, should not happen");494 let name = name.into_flat();495 match super_obj496 .get_for(name.clone(), this.clone())497 .with_description_src(&part.value, || format!("field <{name}> 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(super_obj, name.clone());504505 bail!(NoSuchField(name, suggestions))506 }507 }508 } else {509 evaluate(ctx.clone(), indexable)?510 };511512 for part in parts {513 indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {514 (Val::Obj(v), Val::Str(key)) => match v515 .get(key.clone().into_flat())516 .with_description_src(&part.value, || format!("field <{key}> access"))?517 {518 Some(v) => v,519 #[cfg(feature = "exp-null-coaelse")]520 None if part.null_coaelse => return Ok(Val::Null),521 None => {522 let suggestions = suggest_object_fields(&v, key.clone().into_flat());523524 return Err(Error::from(NoSuchField(525 key.clone().into_flat(),526 suggestions,527 )))528 .with_description_src(&part.value, || format!("field <{key}> access"));529 }530 },531 (Val::Obj(_), n) => bail!(ValueIndexMustBeTypeGot(532 ValType::Obj,533 ValType::Str,534 n.value_type(),535 )),536 (Val::Arr(v), Val::Num(n)) => {537 let n = n.get();538 if n.fract() > f64::EPSILON {539 bail!(FractionalIndex)540 }541 if n < 0.0 {542 bail!(ArrayBoundsError(n as isize, v.len()));543 }544 v.get(n as usize)?545 .ok_or_else(|| ArrayBoundsError(n as isize, v.len()))?546 }547 (Val::Arr(_), Val::Str(n)) => {548 bail!(AttemptedIndexAnArrayWithString(n.into_flat()))549 }550 (Val::Arr(_), n) => bail!(ValueIndexMustBeTypeGot(551 ValType::Arr,552 ValType::Num,553 n.value_type(),554 )),555556 (Val::Str(s), Val::Num(n)) => Val::Str({557 let v: IStr = s558 .clone()559 .into_flat()560 .chars()561 .skip(n.get() as usize)562 .take(1)563 .collect::<String>()564 .into();565 if v.is_empty() {566 let size = s.into_flat().chars().count();567 bail!(StringBoundsError(n.get() as usize, size))568 }569 StrValue::Flat(v)570 }),571 (Val::Str(_), n) => bail!(ValueIndexMustBeTypeGot(572 ValType::Str,573 ValType::Num,574 n.value_type(),575 )),576 #[cfg(feature = "exp-null-coaelse")]577 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),578 (v, _) => bail!(CantIndexInto(v.value_type())),579 };580 }581 Ok(indexable)582 })?,583 LocalExpr(bindings, returned) => {584 let mut new_bindings: GcHashMap<IStr, Thunk<Val>> =585 GcHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum());586 let fctx = Context::new_future();587 for b in bindings {588 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;589 }590 let ctx = ctx.extend(new_bindings, None, None, None).into_future(fctx);591 evaluate(ctx, &returned.clone())?592 }593 Arr(items) => {594 if items.is_empty() {595 Val::Arr(ArrValue::empty())596 } else if items.len() == 1 {597 let item = items[0].clone();598 Val::Arr(ArrValue::lazy(vec![Thunk!(move || evaluate(ctx, &item))]))599 } else {600 Val::Arr(ArrValue::expr(ctx, items.iter().cloned()))601 }602 }603 ArrComp(expr, comp_specs) => {604 let mut out = Vec::new();605 evaluate_comp(ctx, comp_specs, &mut |ctx| {606 let expr = expr.clone();607 out.push(Thunk!(move || evaluate(ctx, &expr)));608 Ok(())609 })?;610 Val::Arr(ArrValue::lazy(out))611 }612 Obj(body) => Val::Obj(evaluate_object(ctx, body)?),613 ObjExtend(a, b) => evaluate_add_op(614 &evaluate(ctx.clone(), a)?,615 &Val::Obj(evaluate_object(ctx, b)?),616 )?,617 Apply(value, args, tailstrict) => ensure_sufficient_stack(|| {618 evaluate_apply(ctx, value, args, CallLocation::new(&loc), *tailstrict)619 })?,620 Function(params, body) => {621 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())622 }623 AssertExpr(assert, returned) => {624 evaluate_assert(ctx.clone(), assert)?;625 evaluate(ctx, returned)?626 }627 ErrorStmt(e) => in_frame(628 CallLocation::new(&loc),629 || "error statement".to_owned(),630 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),631 )?,632 IfElse {633 cond,634 cond_then,635 cond_else,636 } => {637 if in_frame(638 CallLocation::new(&loc),639 || "if condition".to_owned(),640 || bool::from_untyped(evaluate(ctx.clone(), &cond.0)?),641 )? {642 evaluate(ctx, cond_then)?643 } else {644 match cond_else {645 Some(v) => evaluate(ctx, v)?,646 None => Val::Null,647 }648 }649 }650 Slice(value, desc) => {651 fn parse_idx<T: Typed>(652 loc: CallLocation<'_>,653 ctx: &Context,654 expr: Option<&LocExpr>,655 desc: &'static str,656 ) -> Result<Option<T>> {657 if let Some(value) = expr {658 Ok(in_frame(659 loc,660 || format!("slice {desc}"),661 || <Option<T>>::from_untyped(evaluate(ctx.clone(), value)?),662 )?)663 } else {664 Ok(None)665 }666 }667668 let indexable = evaluate(ctx.clone(), value)?;669 let loc = CallLocation::new(&loc);670671 let start = parse_idx(loc, &ctx, desc.start.as_ref(), "start")?;672 let end = parse_idx(loc, &ctx, desc.end.as_ref(), "end")?;673 let step = parse_idx(loc, &ctx, desc.step.as_ref(), "step")?;674675 IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?676 }677 i @ (Import(path) | ImportStr(path) | ImportBin(path)) => {678 let Expr::Str(path) = &path.expr() else {679 bail!("computed imports are not supported")680 };681 let tmp = loc.clone().0;682 let s = ctx.state();683 let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?;684 match i {685 Import(_) => in_frame(686 CallLocation::new(&loc),687 || format!("import {:?}", path.clone()),688 || s.import_resolved(resolved_path),689 )?,690 ImportStr(_) => Val::string(s.import_resolved_str(resolved_path)?),691 ImportBin(_) => Val::Arr(ArrValue::bytes(s.import_resolved_bin(resolved_path)?)),692 _ => unreachable!(),693 }694 }695 })696}