1use std::{collections::HashMap, hash::BuildHasher};23use jrsonnet_interner::IStr;4use jrsonnet_ir::{BindSpec, Destruct};56#[cfg(feature = "exp-preserve-order")]7use crate::evaluate;8use crate::{9 Context, Pending, Thunk, Val, bail,10 error::{ErrorKind::*, Result},11 evaluate_method, evaluate_named_param,12};1314#[allow(clippy::too_many_lines)]15#[allow(unused_variables)]16pub fn destruct<H: BuildHasher>(17 d: &Destruct,18 parent: Thunk<Val>,19 fctx: Pending<Context>,20 new_bindings: &mut HashMap<IStr, Thunk<Val>, H>,21) -> Result<()> {22 match d {23 Destruct::Full(v) => {24 let old = new_bindings.insert(v.clone(), parent);25 if old.is_some() {26 bail!(DuplicateLocalVar(v.clone()))27 }28 }29 #[cfg(feature = "exp-destruct")]30 Destruct::Skip => {}31 #[cfg(feature = "exp-destruct")]32 Destruct::Array { start, rest, end } => {33 use jrsonnet_ir::DestructRest;3435 let min_len = start.len() + end.len();36 let has_rest = rest.is_some();37 let full = Thunk!(move || {38 let v = parent.evaluate()?;39 let Val::Arr(arr) = v else {40 bail!("expected array");41 };42 if !has_rest {43 if arr.len() != min_len {44 bail!("expected {} elements, got {}", min_len, arr.len())45 }46 } else if arr.len() < min_len {47 bail!(48 "expected at least {} elements, but array was only {}",49 min_len,50 arr.len()51 )52 }53 Ok(arr)54 });5556 {57 for (i, d) in start.iter().enumerate() {58 let full = full.clone();59 destruct(60 d,61 Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))),62 fctx.clone(),63 new_bindings,64 )?;65 }66 }6768 match rest {69 Some(DestructRest::Keep(v)) => {70 let start = start.len();71 let end = end.len();72 let full = full.clone();73 destruct(74 &Destruct::Full(v.clone()),75 Thunk!(move || {76 let full = full.evaluate()?;77 let to = full.len() - end;78 Ok(Val::Arr(full.slice(79 Some(start as i32),80 Some(to as i32),81 None,82 )))83 }),84 fctx.clone(),85 new_bindings,86 )?;87 }88 Some(DestructRest::Drop) | None => {}89 }9091 {92 for (i, d) in end.iter().enumerate() {93 let full = full.clone();94 let end = end.len();95 destruct(96 d,97 Thunk!(move || {98 let full = full.evaluate()?;99 Ok(full.get(full.len() - end + i)?.expect("length is checked"))100 }),101 fctx.clone(),102 new_bindings,103 )?;104 }105 }106 }107 #[cfg(feature = "exp-destruct")]108 Destruct::Object { fields, rest } => {109 use jrsonnet_ir::DestructRest;110 use rustc_hash::FxHashSet;111112 use crate::ObjValueBuilder;113114 let captured_fields: FxHashSet<_> = fields.iter().map(|f| f.0.clone()).collect();115 let field_names: Vec<_> = fields116 .iter()117 .map(|f| (f.0.clone(), f.2.is_some()))118 .collect();119 let has_rest = rest.is_some();120 let full = Thunk!(move || {121 let v = parent.evaluate()?;122 let Val::Obj(obj) = v else {123 bail!("expected object");124 };125 for (field, has_default) in &field_names {126 if !has_default && !obj.has_field_ex(field.clone(), true) {127 bail!("missing field: {field}");128 }129 }130 if !has_rest {131 let len = obj.len();132 if len > field_names.len() {133 bail!("too many fields, and rest not found");134 }135 }136 Ok(obj)137 });138139 match rest {140 Some(DestructRest::Keep(v)) => {141 let full = full.clone();142 destruct(143 &Destruct::Full(v.clone()),144 Thunk!(move || {145 let full = full.evaluate()?;146 let mut builder = ObjValueBuilder::new();147 builder.extend_with_core(full.as_standalone());148 builder.with_fields_omitted(captured_fields);149 Ok(Val::Obj(builder.build()))150 }),151 fctx.clone(),152 new_bindings,153 )?;154 }155 Some(DestructRest::Drop) | None => {}156 }157158 for (field, d, default) in fields {159 let default = default.clone().map(|e| (fctx.clone(), e));160 let value = {161 let field = field.clone();162 let full = full.clone();163 Thunk!(move || {164 let full = full.evaluate()?;165 if let Some(field) = full.get(field)? {166 Ok(field)167 } else {168 let (fctx, expr) = default.as_ref().expect("shape is checked");169 Ok(crate::evaluate(fctx.clone().unwrap(), expr)?)170 }171 })172 };173174 if let Some(d) = d {175 destruct(d, value, fctx.clone(), new_bindings)?;176 } else {177 destruct(178 &Destruct::Full(field.clone()),179 value,180 fctx.clone(),181 new_bindings,182 )?;183 }184 }185 }186 }187 Ok(())188}189190pub fn evaluate_dest<H: BuildHasher>(191 d: &BindSpec,192 fctx: Pending<Context>,193 new_bindings: &mut HashMap<IStr, Thunk<Val>, H>,194) -> Result<()> {195 match d {196 BindSpec::Field { into, value } => {197 let name = into.name();198 let value = value.clone();199 let data = {200 let fctx = fctx.clone();201 Thunk!(move || evaluate_named_param(fctx.unwrap(), &value, name))202 };203 destruct(into, data, fctx, new_bindings)?;204 }205 BindSpec::Function {206 name,207 params,208 value,209 } => {210 let params = params.clone();211 let name = name.clone();212 let value = value.clone();213 let old = new_bindings.insert(name.clone(), {214 let name = name.clone();215 Thunk!(move || Ok(evaluate_method(fctx.unwrap(), name, params, value)))216 });217 if old.is_some() {218 bail!(DuplicateLocalVar(name))219 }220 }221 }222 Ok(())223}