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, 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 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() != min_len {33 bail!("expected {} elements, got {}", min_len, arr.len32())34 }35 } else if arr.len() < min_len {36 bail!(37 "expected at least {} elements, but array was only {}",38 min_len,39 arr.len32()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)?.expect("length is checked"))),51 ctx,52 );53 }5455 let start_len = start.len();56 let end_len = end.len();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(start_len..to)))66 }),67 );68 }6970 for (i, d) in end.iter().enumerate() {71 let full = full.clone();72 destruct(73 d,74 fill,75 Thunk!(move || {76 let full = full.evaluate()?;77 Ok(full78 .get(full.len() - end_len + i)?79 .expect("length is checked"))80 }),81 ctx,82 );83 }84}8586#[allow(dead_code, reason = "not dead in exp-destruct")]87fn destruct_object(88 fields: &[LDestructField],89 rest: Option<&LDestructRest>,9091 fill: &LocalsFrame,92 value: Thunk<Val>,93 ctx: &Context,94) {95 use jrsonnet_interner::IStr;96 use rustc_hash::FxHashSet;9798 use crate::{ObjValueBuilder, bail};99100 let captured_fields: FxHashSet<IStr> = fields.iter().map(|f| f.name.clone()).collect();101 let field_names: Vec<(IStr, bool)> = fields102 .iter()103 .map(|f| (f.name.clone(), f.default.is_some()))104 .collect();105 let has_rest = rest.is_some();106 let full = Thunk!(move || {107 let v = value.evaluate()?;108 let Val::Obj(obj) = v else {109 bail!("expected object");110 };111 for (field, has_default) in &field_names {112 if !has_default && !obj.has_field_ex(field.clone(), true) {113 bail!("missing field: {field}");114 }115 }116 if !has_rest {117 let len = obj.len32();118 if len as usize > field_names.len() {119 bail!("too many fields, and rest not found");120 }121 }122 Ok(obj)123 });124125 if let Some(LDestructRest::Keep(slot)) = rest {126 let full = full.clone();127 fill.set(128 *slot,129 Thunk!(move || {130 let full = full.evaluate()?;131 let mut out = ObjValueBuilder::new();132 out.extend_with_core(full.as_standalone());133 out.with_fields_omitted(captured_fields);134 Ok(Val::Obj(out.build()))135 }),136 );137 }138139 for field in fields {140 let field_name = field.name.clone();141 let default_thunk: Option<Thunk<Val>> = field.default.as_ref().map(|(shape, expr)| {142 let expr = expr.clone();143 let env = Context::enter_using(ctx, shape);144 Thunk!(move || evaluate(env, &expr))145 });146147 let field_full = full.clone();148 let value_thunk = Thunk!(move || {149 let obj = field_full.evaluate()?;150 obj.get(field_name)?.map_or_else(151 || default_thunk.as_ref().expect("shape is checked").evaluate(),152 Ok,153 )154 });155156 if let Some(into) = &field.into {157 destruct(into, fill, value_thunk, ctx);158 } else {159 unreachable!("analyzer lowers object-destruct shorthands into `into`");160 }161 }162}163164#[allow(unused_variables)]165pub fn destruct(d: &LDestruct, fill: &LocalsFrame, value: Thunk<Val>, a_ctx: &Context) {166 match d {167 LDestruct::Full(slot) => fill.set(*slot, value),168 #[cfg(feature = "exp-destruct")]169 LDestruct::Skip => {}170 #[cfg(feature = "exp-destruct")]171 LDestruct::Array { start, rest, end } => {172 destruct_array(start, rest.as_ref(), end, fill, value, a_ctx)173 }174 #[cfg(feature = "exp-destruct")]175 LDestruct::Object { fields, rest } => destruct_object(fields, rest.as_ref(), fill, value, a_ctx),176 }177}178179pub fn fill_letrec_binds(fill: &LocalsFrame, ctx: &Context, binds: &[LBind]) {180 for bind in binds {181 let expr = bind.value.clone();182 let env = Context::enter_using(ctx, &bind.value_shape);183 destruct(184 &bind.destruct,185 fill,186 Thunk!(move || evaluate(env, &expr)),187 ctx,188 );189 }190}191192pub fn evaluate_local_expr(parent: Context, l: &LLocalExpr) -> Result<Val> {193 let ctx = parent194 .pack_captures_sup_this(&l.frame_shape)195 .enter(|fill, ctx| {196 fill_letrec_binds(fill, ctx, &l.binds);197 });198 evaluate(ctx, &l.body)199}200201pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}202impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}203204pub fn evaluate_locals_unbound(205 outer: &Context,206 frame_shape: &ClosureShape,207 this_slot: Option<LocalSlot>,208 locals: Rc<Vec<LBind>>,209) -> impl CloneableUnbound<Context> {210 #[derive(Trace, Clone)]211 struct UnboundLocals {212 captures: PackedContext,213 this_slot: Option<LocalSlot>,214 locals: Rc<Vec<LBind>>,215 }216 impl Unbound for UnboundLocals {217 type Bound = Context;218219 fn bind(&self, sup_this: SupThis) -> Result<Context> {220 Ok(self.captures.clone().enter(sup_this, |fill, ctx| {221 if let Some(slot) = self.this_slot {222 let this_obj = ctx.sup_this().expect("sup_this set above").this().clone();223 fill.set(slot, Thunk::evaluated(Val::Obj(this_obj)));224 }225 fill_letrec_binds(fill, ctx, &self.locals);226 }))227 }228 }229230 UnboundLocals {231 captures: outer.pack_captures(frame_shape),232 this_slot,233 locals,234 }235}