difftreelog
feat grow os stack on demand
in: master
3 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -486,6 +486,7 @@
"pathdiff",
"rustc-hash",
"serde",
+ "stacker",
"static_assertions",
"strsim",
"thiserror",
@@ -851,6 +852,15 @@
]
[[package]]
+name = "psm"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874"
+dependencies = [
+ "cc",
+]
+
+[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1077,6 +1087,19 @@
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
+name = "stacker"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "libc",
+ "psm",
+ "winapi",
+]
+
+[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1203,6 +1226,28 @@
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -60,3 +60,4 @@
# Bigint
num-bigint = { workspace = true, features = ["serde"], optional = true }
derivative.workspace = true
+stacker = "0.1.15"
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth1use 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, ThunkValue},22 Context, Error, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,23 ResultExt, Unbound, Val,24};25pub mod destructure;26pub mod operator;2728// This is the amount of bytes that need to be left on the stack before increasing the size.29// It must be at least as large as the stack required by any code that does not call30// `ensure_sufficient_stack`.31const RED_ZONE: usize = 100 * 1024; // 100k3233// Only the first stack that is pushed, grows exponentially (2^n * STACK_PER_RECURSION) from then34// on. This flag has performance relevant characteristics. Don't set it too high.35const STACK_PER_RECURSION: usize = 1024 * 1024; // 1MB3637/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations38/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit39/// from this.40///41/// Should not be sprinkled around carelessly, as it causes a little bit of overhead.42#[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 // TODO: Should there be ability to preserve iteration order?139 #[cfg(feature = "exp-preserve-order")]140 false,141 ) {142 #[derive(Trace)]143 struct ObjectFieldThunk {144 obj: ObjValue,145 field: IStr,146 }147 impl ThunkValue for ObjectFieldThunk {148 type Output = Val;149150 fn get(self: Box<Self>) -> Result<Self::Output> {151 self.obj.get(self.field).transpose().expect(152 "field exists, as field name was obtained from object.fields()",153 )154 }155 }156157 let fctx = Pending::new();158 let mut new_bindings = GcHashMap::with_capacity(var.capacity_hint());159 let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![160 Thunk::evaluated(Val::string(field.clone())),161 Thunk::new(ObjectFieldThunk {162 field: field.clone(),163 obj: obj.clone(),164 }),165 ])));166 destruct(var, value, fctx.clone(), &mut new_bindings)?;167 let ctx = ctx168 .clone()169 .extend(new_bindings, None, None, None)170 .into_future(fctx);171172 evaluate_comp(ctx, &specs[1..], callback)?;173 }174 }175 _ => bail!(InComprehensionCanOnlyIterateOverArray),176 },177 }178 Ok(())179}180181trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}182impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}183184fn evaluate_object_locals(185 fctx: Pending<Context>,186 locals: Rc<Vec<BindSpec>>,187) -> impl CloneableUnbound<Context> {188 #[derive(Trace, Clone)]189 struct UnboundLocals {190 fctx: Pending<Context>,191 locals: Rc<Vec<BindSpec>>,192 }193 impl Unbound for UnboundLocals {194 type Bound = Context;195196 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Context> {197 let fctx = Context::new_future();198 let mut new_bindings =199 GcHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum());200 for b in self.locals.iter() {201 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;202 }203204 let ctx = self.fctx.unwrap();205 let new_dollar = ctx.dollar().cloned().or_else(|| this.clone());206207 let ctx = ctx208 .extend(new_bindings, new_dollar, sup, this)209 .into_future(fctx);210211 Ok(ctx)212 }213 }214215 UnboundLocals { fctx, locals }216}217218pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(219 builder: &mut ObjValueBuilder,220 ctx: Context,221 uctx: B,222 field: &FieldMember,223) -> Result<()> {224 let name = evaluate_field_name(ctx, &field.name)?;225 let Some(name) = name else {226 return Ok(());227 };228229 match field {230 FieldMember {231 plus,232 params: None,233 visibility,234 value,235 ..236 } => {237 #[derive(Trace)]238 struct UnboundValue<B: Trace> {239 uctx: B,240 value: LocExpr,241 name: IStr,242 }243 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {244 type Bound = Val;245 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {246 evaluate_named(self.uctx.bind(sup, this)?, &self.value, self.name.clone())247 }248 }249250 builder251 .field(name.clone())252 .with_add(*plus)253 .with_visibility(*visibility)254 .with_location(value.span())255 .bindable(UnboundValue {256 uctx,257 value: value.clone(),258 name,259 })?;260 }261 FieldMember {262 params: Some(params),263 visibility,264 value,265 ..266 } => {267 #[derive(Trace)]268 struct UnboundMethod<B: Trace> {269 uctx: B,270 value: LocExpr,271 params: ParamsDesc,272 name: IStr,273 }274 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {275 type Bound = Val;276 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {277 Ok(evaluate_method(278 self.uctx.bind(sup, this)?,279 self.name.clone(),280 self.params.clone(),281 self.value.clone(),282 ))283 }284 }285286 builder287 .field(name.clone())288 .with_visibility(*visibility)289 .with_location(value.span())290 .bindable(UnboundMethod {291 uctx,292 value: value.clone(),293 params: params.clone(),294 name,295 })?;296 }297 }298 Ok(())299}300301#[allow(clippy::too_many_lines)]302pub fn evaluate_member_list_object(ctx: Context, members: &[Member]) -> Result<ObjValue> {303 let mut builder = ObjValueBuilder::new();304 let locals = Rc::new(305 members306 .iter()307 .filter_map(|m| match m {308 Member::BindStmt(bind) => Some(bind.clone()),309 _ => None,310 })311 .collect::<Vec<_>>(),312 );313314 let fctx = Context::new_future();315316 // We have single context for all fields, so we can cache binds317 let uctx = CachedUnbound::new(evaluate_object_locals(fctx.clone(), locals));318319 for member in members {320 match member {321 Member::Field(field) => {322 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;323 }324 Member::AssertStmt(stmt) => {325 #[derive(Trace)]326 struct ObjectAssert<B: Trace> {327 uctx: B,328 assert: AssertStmt,329 }330 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {331 fn run(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<()> {332 let ctx = self.uctx.bind(sup, this)?;333 evaluate_assert(ctx, &self.assert)334 }335 }336 builder.assert(ObjectAssert {337 uctx: uctx.clone(),338 assert: stmt.clone(),339 });340 }341 Member::BindStmt(_) => {342 // Already handled343 }344 }345 }346 let this = builder.build();347 fctx.fill(ctx.extend(GcHashMap::new(), None, None, Some(this.clone())));348 Ok(this)349}350351pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {352 Ok(match object {353 ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,354 ObjBody::ObjComp(obj) => {355 let mut builder = ObjValueBuilder::new();356 let locals = Rc::new(357 obj.pre_locals358 .iter()359 .chain(obj.post_locals.iter())360 .cloned()361 .collect::<Vec<_>>(),362 );363 let mut ctxs = vec![];364 evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {365 let fctx = Context::new_future();366 ctxs.push((ctx.clone(), fctx.clone()));367 let uctx = evaluate_object_locals(fctx, locals.clone());368369 evaluate_field_member(&mut builder, ctx, uctx, &obj.field)370 })?;371372 let this = builder.build();373 for (ctx, fctx) in ctxs {374 let _ctx = ctx375 .extend(GcHashMap::new(), None, None, Some(this.clone()))376 .into_future(fctx);377 }378 this379 }380 })381}382383pub fn evaluate_apply(384 ctx: Context,385 value: &LocExpr,386 args: &ArgsDesc,387 loc: CallLocation<'_>,388 tailstrict: bool,389) -> Result<Val> {390 let value = evaluate(ctx.clone(), value)?;391 Ok(match value {392 Val::Func(f) => {393 let body = || f.evaluate(ctx, loc, args, tailstrict);394 if tailstrict {395 body()?396 } else {397 in_frame(loc, || format!("function <{}> call", f.name()), body)?398 }399 }400 v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())),401 })402}403404pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {405 let value = &assertion.0;406 let msg = &assertion.1;407 let assertion_result = in_frame(408 CallLocation::new(&value.span()),409 || "assertion condition".to_owned(),410 || bool::from_untyped(evaluate(ctx.clone(), value)?),411 )?;412 if !assertion_result {413 in_frame(414 CallLocation::new(&value.span()),415 || "assertion failure".to_owned(),416 || {417 if let Some(msg) = msg {418 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));419 }420 bail!(AssertionFailed(Val::Null.to_string()?));421 },422 )?;423 }424 Ok(())425}426427pub fn evaluate_named(ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {428 use Expr::*;429 Ok(match expr.expr() {430 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),431 _ => evaluate(ctx, expr)?,432 })433}434435#[allow(clippy::too_many_lines)]436pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result<Val> {437 use Expr::*;438439 if let Some(trivial) = evaluate_trivial(expr) {440 return Ok(trivial);441 }442 let loc = expr.span();443 Ok(match expr.expr() {444 Literal(LiteralType::This) => {445 Val::Obj(ctx.this().ok_or(CantUseSelfOutsideOfObject)?.clone())446 }447 Literal(LiteralType::Super) => Val::Obj(448 ctx.super_obj().ok_or(NoSuperFound)?.with_this(449 ctx.this()450 .expect("if super exists - then this should too")451 .clone(),452 ),453 ),454 Literal(LiteralType::Dollar) => {455 Val::Obj(ctx.dollar().ok_or(NoTopLevelObjectFound)?.clone())456 }457 Literal(LiteralType::True) => Val::Bool(true),458 Literal(LiteralType::False) => Val::Bool(false),459 Literal(LiteralType::Null) => Val::Null,460 Parened(e) => evaluate(ctx, e)?,461 Str(v) => Val::string(v.clone()),462 Num(v) => Val::try_num(*v)?,463 // I have tried to remove special behavior from super by implementing standalone-super464 // expresion, but looks like this case still needs special treatment.465 //466 // Note that other jsonnet implementations will fail on `if value in (super)` expression,467 // because the standalone super literal is not supported, that is because in other468 // implementations `in super` treated differently from in `smth_else`.469 BinaryOp(field, BinaryOpType::In, e)470 if matches!(e.expr(), Expr::Literal(LiteralType::Super)) =>471 {472 let Some(super_obj) = ctx.super_obj() else {473 return Ok(Val::Bool(false));474 };475 let field = evaluate(ctx.clone(), field)?;476 Val::Bool(super_obj.has_field_ex(field.to_string()?, true))477 }478 BinaryOp(v1, o, v2) => evaluate_binary_op_special(ctx, v1, *o, v2)?,479 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,480 Var(name) => in_frame(481 CallLocation::new(&loc),482 || format!("variable <{name}> access"),483 || ctx.binding(name.clone())?.evaluate(),484 )?,485 Index { indexable, parts } => ensure_sufficient_stack(|| {486 let mut parts = parts.iter();487 let mut indexable = if matches!(indexable.expr(), Expr::Literal(LiteralType::Super)) {488 let part = parts.next().expect("at least part should exist");489 let Some(super_obj) = ctx.super_obj() else {490 #[cfg(feature = "exp-null-coaelse")]491 if part.null_coaelse {492 return Ok(Val::Null);493 }494 bail!(NoSuperFound)495 };496 let name = evaluate(ctx.clone(), &part.value)?;497498 let Val::Str(name) = name else {499 bail!(ValueIndexMustBeTypeGot(500 ValType::Obj,501 ValType::Str,502 name.value_type(),503 ))504 };505506 let this = ctx507 .this()508 .expect("no this found, while super present, should not happen");509 let name = name.into_flat();510 match super_obj511 .get_for(name.clone(), this.clone())512 .with_description_src(&part.value, || format!("field <{name}> access"))?513 {514 Some(v) => v,515 #[cfg(feature = "exp-null-coaelse")]516 None if part.null_coaelse => return Ok(Val::Null),517 None => {518 let suggestions = suggest_object_fields(super_obj, name.clone());519520 bail!(NoSuchField(name, suggestions))521 }522 }523 } else {524 evaluate(ctx.clone(), indexable)?525 };526527 for part in parts {528 indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {529 (Val::Obj(v), Val::Str(key)) => match v530 .get(key.clone().into_flat())531 .with_description_src(&part.value, || format!("field <{key}> access"))?532 {533 Some(v) => v,534 #[cfg(feature = "exp-null-coaelse")]535 None if part.null_coaelse => return Ok(Val::Null),536 None => {537 let suggestions = suggest_object_fields(&v, key.clone().into_flat());538539 return Err(Error::from(NoSuchField(540 key.clone().into_flat(),541 suggestions,542 )))543 .with_description_src(&part.value, || format!("field <{key}> access"));544 }545 },546 (Val::Obj(_), n) => bail!(ValueIndexMustBeTypeGot(547 ValType::Obj,548 ValType::Str,549 n.value_type(),550 )),551 (Val::Arr(v), Val::Num(n)) => {552 let n = n.get();553 if n.fract() > f64::EPSILON {554 bail!(FractionalIndex)555 }556 if n < 0.0 {557 bail!(ArrayBoundsError(n as isize, v.len()));558 }559 v.get(n as usize)?560 .ok_or_else(|| ArrayBoundsError(n as isize, v.len()))?561 }562 (Val::Arr(_), Val::Str(n)) => {563 bail!(AttemptedIndexAnArrayWithString(n.into_flat()))564 }565 (Val::Arr(_), n) => bail!(ValueIndexMustBeTypeGot(566 ValType::Arr,567 ValType::Num,568 n.value_type(),569 )),570571 (Val::Str(s), Val::Num(n)) => Val::Str({572 let v: IStr = s573 .clone()574 .into_flat()575 .chars()576 .skip(n.get() as usize)577 .take(1)578 .collect::<String>()579 .into();580 if v.is_empty() {581 let size = s.into_flat().chars().count();582 bail!(StringBoundsError(n.get() as usize, size))583 }584 StrValue::Flat(v)585 }),586 (Val::Str(_), n) => bail!(ValueIndexMustBeTypeGot(587 ValType::Str,588 ValType::Num,589 n.value_type(),590 )),591 #[cfg(feature = "exp-null-coaelse")]592 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),593 (v, _) => bail!(CantIndexInto(v.value_type())),594 };595 }596 Ok(indexable)597 })?,598 LocalExpr(bindings, returned) => {599 let mut new_bindings: GcHashMap<IStr, Thunk<Val>> =600 GcHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum());601 let fctx = Context::new_future();602 for b in bindings {603 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;604 }605 let ctx = ctx.extend(new_bindings, None, None, None).into_future(fctx);606 evaluate(ctx, &returned.clone())?607 }608 Arr(items) => {609 if items.is_empty() {610 Val::Arr(ArrValue::empty())611 } else if items.len() == 1 {612 #[derive(Trace)]613 struct ArrayElement {614 ctx: Context,615 item: LocExpr,616 }617 impl ThunkValue for ArrayElement {618 type Output = Val;619 fn get(self: Box<Self>) -> Result<Val> {620 evaluate(self.ctx, &self.item)621 }622 }623 Val::Arr(ArrValue::lazy(vec![Thunk::new(ArrayElement {624 ctx,625 item: items[0].clone(),626 })]))627 } else {628 Val::Arr(ArrValue::expr(ctx, items.iter().cloned()))629 }630 }631 ArrComp(expr, comp_specs) => {632 let mut out = Vec::new();633 evaluate_comp(ctx, comp_specs, &mut |ctx| {634 #[derive(Trace)]635 struct EvaluateThunk {636 ctx: Context,637 expr: LocExpr,638 }639 impl ThunkValue for EvaluateThunk {640 type Output = Val;641 fn get(self: Box<Self>) -> Result<Val> {642 evaluate(self.ctx, &self.expr)643 }644 }645 out.push(Thunk::new(EvaluateThunk {646 ctx,647 expr: expr.clone(),648 }));649 Ok(())650 })?;651 Val::Arr(ArrValue::lazy(out))652 }653 Obj(body) => Val::Obj(evaluate_object(ctx, body)?),654 ObjExtend(a, b) => evaluate_add_op(655 &evaluate(ctx.clone(), a)?,656 &Val::Obj(evaluate_object(ctx, b)?),657 )?,658 Apply(value, args, tailstrict) => ensure_sufficient_stack(|| {659 evaluate_apply(ctx, value, args, CallLocation::new(&loc), *tailstrict)660 })?,661 Function(params, body) => {662 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())663 }664 AssertExpr(assert, returned) => {665 evaluate_assert(ctx.clone(), assert)?;666 evaluate(ctx, returned)?667 }668 ErrorStmt(e) => in_frame(669 CallLocation::new(&loc),670 || "error statement".to_owned(),671 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),672 )?,673 IfElse {674 cond,675 cond_then,676 cond_else,677 } => {678 if in_frame(679 CallLocation::new(&loc),680 || "if condition".to_owned(),681 || bool::from_untyped(evaluate(ctx.clone(), &cond.0)?),682 )? {683 evaluate(ctx, cond_then)?684 } else {685 match cond_else {686 Some(v) => evaluate(ctx, v)?,687 None => Val::Null,688 }689 }690 }691 Slice(value, desc) => {692 fn parse_idx<T: Typed>(693 loc: CallLocation<'_>,694 ctx: &Context,695 expr: Option<&LocExpr>,696 desc: &'static str,697 ) -> Result<Option<T>> {698 if let Some(value) = expr {699 Ok(Some(in_frame(700 loc,701 || format!("slice {desc}"),702 || T::from_untyped(evaluate(ctx.clone(), value)?),703 )?))704 } else {705 Ok(None)706 }707 }708709 let indexable = evaluate(ctx.clone(), value)?;710 let loc = CallLocation::new(&loc);711712 let start = parse_idx(loc, &ctx, desc.start.as_ref(), "start")?;713 let end = parse_idx(loc, &ctx, desc.end.as_ref(), "end")?;714 let step = parse_idx(loc, &ctx, desc.step.as_ref(), "step")?;715716 IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?717 }718 i @ (Import(path) | ImportStr(path) | ImportBin(path)) => {719 let Expr::Str(path) = &path.expr() else {720 bail!("computed imports are not supported")721 };722 let tmp = loc.clone().0;723 let s = ctx.state();724 let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?;725 match i {726 Import(_) => in_frame(727 CallLocation::new(&loc),728 || format!("import {:?}", path.clone()),729 || s.import_resolved(resolved_path),730 )?,731 ImportStr(_) => Val::string(s.import_resolved_str(resolved_path)?),732 ImportBin(_) => Val::Arr(ArrValue::bytes(s.import_resolved_bin(resolved_path)?)),733 _ => unreachable!(),734 }735 }736 })737}