1use std::rc::Rc;23use jrsonnet_gcmodule::Trace;45use crate::{6 Context, LocalsFrame, PackedContext, Result, SupThis, Thunk, Unbound, Val,7 analyze::{8 ClosureShape, LBind, LDestruct, LDestructField, LDestructRest, LExpr, LLocalExpr, LocalSlot,9 },10 bail,11 evaluate::evaluate,12};1314#[allow(dead_code, reason = "not dead in exp-destruct")]15fn destruct_array(16 start: &[LDestruct],17 rest: Option<&LDestructRest>,18 end: &[LDestruct],1920 fill: &LocalsFrame,21 value: Thunk<Val>,22 a_ctx: &Context,23) {24 let min_len = start.len() + end.len();25 let has_rest = rest.is_some();26 let full = Thunk!(move || {27 let v = value.evaluate()?;28 let Val::Arr(arr) = v else {29 bail!("expected array");30 };31 if !has_rest {32 if arr.len() as usize != min_len {33 bail!("expected {} elements, got {}", min_len, arr.len())34 }35 } else if (arr.len() as usize) < min_len {36 bail!(37 "expected at least {} elements, but array was only {}",38 min_len,39 arr.len()40 )41 }42 Ok(arr)43 });4445 for (i, d) in start.iter().enumerate() {46 let full = full.clone();47 destruct(48 d,49 fill,50 Thunk!(move || Ok(full.evaluate()?.get(i as u32)?.expect("length is checked"))),51 a_ctx,52 );53 }5455 let start_len = start.len() as u32;56 let end_len = end.len() as u32;5758 if let Some(LDestructRest::Keep(slot)) = rest {59 let full = full.clone();60 fill.set(61 *slot,62 Thunk!(move || {63 let full = full.evaluate()?;64 let to = full.len() - end_len;65 Ok(Val::Arr(full.slice(66 Some(start_len as i32),67 Some(to as i32),68 None,69 )))70 }),71 );72 }7374 for (i, d) in end.iter().enumerate() {75 let full = full.clone();76 destruct(77 d,78 fill,79 Thunk!(move || {80 let full = full.evaluate()?;81 Ok(full82 .get(full.len() - end_len + i as u32)?83 .expect("length is checked"))84 }),85 a_ctx,86 );87 }88}8990#[allow(dead_code, reason = "not dead in exp-destruct")]91fn destruct_object(92 fields: &[LDestructField],93 rest: Option<&LDestructRest>,9495 fill: &LocalsFrame,96 value: Thunk<Val>,97 a_ctx: &Context,98) {99 use jrsonnet_interner::IStr;100 use rustc_hash::FxHashSet;101102 use crate::{ObjValueBuilder, bail};103104 let captured_fields: FxHashSet<IStr> = fields.iter().map(|f| f.name.clone()).collect();105 let field_names: Vec<(IStr, bool)> = fields106 .iter()107 .map(|f| (f.name.clone(), f.default.is_some()))108 .collect();109 let has_rest = rest.is_some();110 let full = Thunk!(move || {111 let v = value.evaluate()?;112 let Val::Obj(obj) = v else {113 bail!("expected object");114 };115 for (field, has_default) in &field_names {116 if !has_default && !obj.has_field_ex(field.clone(), true) {117 bail!("missing field: {field}");118 }119 }120 if !has_rest {121 let len = obj.len();122 if len as usize > field_names.len() {123 bail!("too many fields, and rest not found");124 }125 }126 Ok(obj)127 });128129 if let Some(LDestructRest::Keep(slot)) = rest {130 let full = full.clone();131 fill.set(132 *slot,133 Thunk!(move || {134 let full = full.evaluate()?;135 let mut out = ObjValueBuilder::new();136 out.extend_with_core(full.as_standalone());137 out.with_fields_omitted(captured_fields);138 Ok(Val::Obj(out.build()))139 }),140 );141 }142143 for field in fields {144 let field_name = field.name.clone();145 let default_thunk: Option<Thunk<Val>> = field146 .default147 .as_ref()148 .map(|(shape, expr)| build_b_thunk(a_ctx, shape, expr.clone()));149150 let field_full = full.clone();151 let value_thunk = Thunk!(move || {152 let obj = field_full.evaluate()?;153 obj.get(field_name)?.map_or_else(154 || default_thunk.as_ref().expect("shape is checked").evaluate(),155 Ok,156 )157 });158159 if let Some(into) = &field.into {160 destruct(into, fill, value_thunk, a_ctx);161 } else {162 unreachable!("analyzer lowers object-destruct shorthands into `into`");163 }164 }165}166167#[allow(unused_variables)]168pub fn destruct(d: &LDestruct, fill: &LocalsFrame, value: Thunk<Val>, a_ctx: &Context) {169 match d {170 LDestruct::Full(slot) => fill.set(*slot, value),171 #[cfg(feature = "exp-destruct")]172 LDestruct::Skip => {}173 #[cfg(feature = "exp-destruct")]174 LDestruct::Array { start, rest, end } => {175 destruct_array(start, rest.as_ref(), end, fill, value, a_ctx)176 }177 #[cfg(feature = "exp-destruct")]178 LDestruct::Object { fields, rest } => destruct_object(fields, rest.as_ref(), fill, value, a_ctx),179 }180}181182pub fn build_b_thunk(a_ctx: &Context, shape: &ClosureShape, expr: Rc<LExpr>) -> Thunk<Val> {183 let env = Context::enter_using(a_ctx, shape);184 Thunk!(move || evaluate(env, &expr))185}186pub fn build_b_thunk_uno(a_ctx: &Context, shape: Rc<(ClosureShape, LExpr)>) -> Thunk<Val> {187 let env = Context::enter_using(a_ctx, &shape.0);188 Thunk!(move || evaluate(env, &shape.1))189}190191pub fn fill_letrec_binds(fill: &LocalsFrame, ctx: &Context, binds: &[LBind]) {192 for bind in binds {193 let value_thunk = build_b_thunk(ctx, &bind.value_shape, bind.value.clone());194 destruct(&bind.destruct, fill, value_thunk, ctx);195 }196}197198pub fn evaluate_local_expr(parent: Context, l: &LLocalExpr) -> Result<Val> {199 let ctx = parent200 .pack_captures_sup_this(&l.frame_shape)201 .enter(|fill, ctx| {202 fill_letrec_binds(fill, ctx, &l.binds);203 });204 evaluate(ctx, &l.body)205}206207pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}208impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}209210pub fn evaluate_locals_unbound(211 outer: &Context,212 frame_shape: &ClosureShape,213 this_slot: Option<LocalSlot>,214 locals: Rc<Vec<LBind>>,215) -> impl CloneableUnbound<Context> {216 #[derive(Trace, Clone)]217 struct UnboundLocals {218 captures: PackedContext,219 this_slot: Option<LocalSlot>,220 locals: Rc<Vec<LBind>>,221 }222 impl Unbound for UnboundLocals {223 type Bound = Context;224225 fn bind(&self, sup_this: SupThis) -> Result<Context> {226 Ok(self.captures.clone().enter(sup_this, |fill, ctx| {227 if let Some(slot) = self.this_slot {228 let this_obj = ctx.sup_this().expect("sup_this set above").this().clone();229 fill.set(slot, Thunk::evaluated(Val::Obj(this_obj)));230 }231 fill_letrec_binds(fill, ctx, &self.locals);232 }))233 }234 }235236 UnboundLocals {237 captures: outer.pack_captures(frame_shape),238 this_slot,239 locals,240 }241}