1use jrsonnet_interner::IStr;2use jrsonnet_parser::{BindSpec, Destruct};3use rustc_hash::FxHashMap;45use crate::{6 bail,7 error::{ErrorKind::*, Result},8 evaluate, evaluate_method, evaluate_named, Context, Pending, Thunk, Val,9};1011#[allow(clippy::too_many_lines)]12#[allow(unused_variables)]13pub fn destruct(14 d: &Destruct,15 parent: Thunk<Val>,16 fctx: Pending<Context>,17 new_bindings: &mut FxHashMap<IStr, Thunk<Val>>,18) -> Result<()> {19 match d {20 Destruct::Full(v) => {21 let old = new_bindings.insert(v.clone(), parent);22 if old.is_some() {23 bail!(DuplicateLocalVar(v.clone()))24 }25 }26 #[cfg(feature = "exp-destruct")]27 Destruct::Skip => {}28 #[cfg(feature = "exp-destruct")]29 Destruct::Array { start, rest, end } => {30 use jrsonnet_parser::DestructRest;3132 let min_len = start.len() + end.len();33 let has_rest = rest.is_some();34 let full = Thunk!(move || {35 let v = parent.evaluate()?;36 let Val::Arr(arr) = v else {37 bail!("expected array");38 };39 if !has_rest {40 if arr.len() != min_len {41 bail!("expected {} elements, got {}", min_len, arr.len())42 }43 } else if arr.len() < min_len {44 bail!(45 "expected at least {} elements, but array was only {}",46 min_len,47 arr.len()48 )49 }50 Ok(arr)51 });5253 {54 for (i, d) in start.iter().enumerate() {55 let full = full.clone();56 destruct(57 d,58 Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))),59 fctx.clone(),60 new_bindings,61 )?;62 }63 }6465 match rest {66 Some(DestructRest::Keep(v)) => {67 let start = start.len();68 let end = end.len();69 let full = full.clone();70 destruct(71 &Destruct::Full(v.clone()),72 Thunk!(move || {73 let full = full.evaluate()?;74 let to = full.len() - end;75 Ok(Val::Arr(full.slice(76 Some(start as i32),77 Some(to as i32),78 None,79 )))80 }),81 fctx.clone(),82 new_bindings,83 )?;84 }85 Some(DestructRest::Drop) | None => {}86 }8788 {89 for (i, d) in end.iter().enumerate() {90 let full = full.clone();91 let end = end.len();92 destruct(93 d,94 Thunk!(move || {95 let full = full.evaluate()?;96 Ok(full.get(full.len() - end + i)?.expect("length is checked"))97 }),98 fctx.clone(),99 new_bindings,100 )?;101 }102 }103 }104 #[cfg(feature = "exp-destruct")]105 Destruct::Object { fields, rest } => {106 let field_names: Vec<_> = fields107 .iter()108 .map(|f| (f.0.clone(), f.2.is_some()))109 .collect();110 let has_rest = rest.is_some();111 let full = Thunk!(move || {112 let v = parent.evaluate()?;113 let Val::Obj(obj) = v else {114 bail!("expected object");115 };116 for (field, has_default) in &field_names {117 if !has_default && !obj.has_field_ex(field.clone(), true) {118 bail!("missing field: {field}");119 }120 }121 if !has_rest {122 let len = obj.len();123 if len > field_names.len() {124 bail!("too many fields, and rest not found");125 }126 }127 Ok(obj)128 });129130 for (field, d, default) in fields {131 let default = default.clone().map(|e| (fctx.clone(), e));132 let value = {133 let field = field.clone();134 let full = full.clone();135 Thunk!(move || {136 let full = full.evaluate()?;137 if let Some(field) = full.get(field)? {138 Ok(field)139 } else {140 let (fctx, expr) = default.as_ref().expect("shape is checked");141 Ok(evaluate(fctx.clone().unwrap(), expr)?)142 }143 })144 };145146 if let Some(d) = d {147 destruct(d, value, fctx.clone(), new_bindings)?;148 } else {149 destruct(150 &Destruct::Full(field.clone()),151 value,152 fctx.clone(),153 new_bindings,154 )?;155 }156 }157 }158 }159 Ok(())160}161162pub fn evaluate_dest(163 d: &BindSpec,164 fctx: Pending<Context>,165 new_bindings: &mut FxHashMap<IStr, Thunk<Val>>,166) -> Result<()> {167 match d {168 BindSpec::Field { into, value } => {169 let name = into.name();170 let value = value.clone();171 let data = {172 let fctx = fctx.clone();173 Thunk!(move || name.map_or_else(174 || evaluate(fctx.unwrap(), &value),175 |name| evaluate_named(fctx.unwrap(), &value, name),176 ))177 };178 destruct(into, data, fctx, new_bindings)?;179 }180 BindSpec::Function {181 name,182 params,183 value,184 } => {185 let params = params.clone();186 let name = name.clone();187 let value = value.clone();188 let old = new_bindings.insert(name.clone(), {189 let name = name.clone();190 Thunk!(move || Ok(evaluate_method(fctx.unwrap(), name, params, value)))191 });192 if old.is_some() {193 bail!(DuplicateLocalVar(name))194 }195 }196 }197 Ok(())198}