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