1use jrsonnet_gcmodule::Trace;2use jrsonnet_interner::IStr;3use jrsonnet_parser::{BindSpec, Destruct, LocExpr, ParamsDesc};45use crate::{6 error::{Error::*, Result},7 evaluate, evaluate_method, evaluate_named,8 gc::GcHashMap,9 tb, throw,10 val::ThunkValue,11 Context, Pending, Thunk, Val,12};1314#[allow(clippy::too_many_lines)]15#[allow(unused_variables)]16pub fn destruct(17 d: &Destruct,18 parent: Thunk<Val>,19 fctx: Pending<Context>,20 new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,21) -> Result<()> {22 match d {23 Destruct::Full(v) => {24 let old = new_bindings.insert(v.clone(), parent);25 if old.is_some() {26 throw!(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_parser::DestructRest;3435 use crate::val::ArrValue;3637 #[derive(Trace)]38 struct DataThunk {39 parent: Thunk<Val>,40 min_len: usize,41 has_rest: bool,42 }43 impl ThunkValue for DataThunk {44 type Output = ArrValue;4546 fn get(self: Box<Self>) -> Result<Self::Output> {47 let v = self.parent.evaluate()?;48 let Val::Arr(arr) = v else {49 throw!("expected array");50 };51 if !self.has_rest {52 if arr.len() != self.min_len {53 throw!("expected {} elements, got {}", self.min_len, arr.len())54 }55 } else if arr.len() < self.min_len {56 throw!(57 "expected at least {} elements, but array was only {}",58 self.min_len,59 arr.len()60 )61 }62 Ok(arr)63 }64 }6566 let full = Thunk::new(tb!(DataThunk {67 min_len: start.len() + end.len(),68 has_rest: rest.is_some(),69 parent,70 }));7172 {73 #[derive(Trace)]74 struct BaseThunk {75 full: Thunk<ArrValue>,76 index: usize,77 }78 impl ThunkValue for BaseThunk {79 type Output = Val;8081 fn get(self: Box<Self>) -> Result<Self::Output> {82 let full = self.full.evaluate()?;83 Ok(full.get(self.index)?.expect("length is checked"))84 }85 }86 for (i, d) in start.iter().enumerate() {87 destruct(88 d,89 Thunk::new(tb!(BaseThunk {90 full: full.clone(),91 index: i,92 })),93 fctx.clone(),94 new_bindings,95 )?;96 }97 }9899 match rest {100 Some(DestructRest::Keep(v)) => {101 #[derive(Trace)]102 struct RestThunk {103 full: Thunk<ArrValue>,104 start: usize,105 end: usize,106 }107 impl ThunkValue for RestThunk {108 type Output = Val;109110 fn get(self: Box<Self>) -> Result<Self::Output> {111 let full = self.full.evaluate()?;112 let to = full.len() - self.end;113 Ok(Val::Arr(full.slice(Some(self.start), Some(to), None)))114 }115 }116117 destruct(118 &Destruct::Full(v.clone()),119 Thunk::new(tb!(RestThunk {120 full: full.clone(),121 start: start.len(),122 end: end.len(),123 })),124 fctx.clone(),125 new_bindings,126 )?;127 }128 Some(DestructRest::Drop) => {}129 None => {}130 }131132 {133 #[derive(Trace)]134 struct EndThunk {135 full: Thunk<ArrValue>,136 index: usize,137 end: usize,138 }139 impl ThunkValue for EndThunk {140 type Output = Val;141142 fn get(self: Box<Self>) -> Result<Self::Output> {143 let full = self.full.evaluate()?;144 Ok(full145 .get(full.len() - self.end + self.index)?146 .expect("length is checked"))147 }148 }149 for (i, d) in end.iter().enumerate() {150 destruct(151 d,152 Thunk::new(tb!(EndThunk {153 full: full.clone(),154 index: i,155 end: end.len(),156 })),157 fctx.clone(),158 new_bindings,159 )?;160 }161 }162 }163 #[cfg(feature = "exp-destruct")]164 Destruct::Object { fields, rest } => {165 use crate::obj::ObjValue;166167 #[derive(Trace)]168 struct DataThunk {169 parent: Thunk<Val>,170 field_names: Vec<IStr>,171 has_rest: bool,172 }173 impl ThunkValue for DataThunk {174 type Output = ObjValue;175176 fn get(self: Box<Self>) -> Result<Self::Output> {177 let v = self.parent.evaluate()?;178 let Val::Obj(obj) = v else {179 throw!("expected object");180 };181 for field in &self.field_names {182 if !obj.has_field_ex(field.clone(), true) {183 throw!("missing field: {}", field);184 }185 }186 if !self.has_rest {187 let len = obj.len();188 if len != self.field_names.len() {189 throw!("too many fields, and rest not found");190 }191 }192 Ok(obj)193 }194 }195 let field_names: Vec<_> = fields196 .iter()197 .filter(|f| f.2.is_none())198 .map(|f| f.0.clone())199 .collect();200 let full = Thunk::new(tb!(DataThunk {201 parent,202 field_names: field_names.clone(),203 has_rest: rest.is_some()204 }));205206 for (field, d, default) in fields {207 #[derive(Trace)]208 struct FieldThunk {209 full: Thunk<ObjValue>,210 field: IStr,211 default: Option<(Pending<Context>, LocExpr)>,212 }213 impl ThunkValue for FieldThunk {214 type Output = Val;215216 fn get(self: Box<Self>) -> Result<Self::Output> {217 let full = self.full.evaluate()?;218 if let Some(field) = full.get(self.field)? {219 Ok(field)220 } else {221 let (fctx, expr) = self.default.as_ref().expect("shape is checked");222 Ok(evaluate(fctx.clone().unwrap(), &expr)?)223 }224 }225 }226 let value = Thunk::new(tb!(FieldThunk {227 full: full.clone(),228 field: field.clone(),229 default: default.clone().map(|e| (fctx.clone(), e)),230 }));231 if let Some(d) = d {232 destruct(d, value, fctx.clone(), new_bindings)?;233 } else {234 destruct(235 &Destruct::Full(field.clone()),236 value,237 fctx.clone(),238 new_bindings,239 )?;240 }241 }242 }243 }244 Ok(())245}246247pub fn evaluate_dest(248 d: &BindSpec,249 fctx: Pending<Context>,250 new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,251) -> Result<()> {252 match d {253 BindSpec::Field { into, value } => {254 #[derive(Trace)]255 struct EvaluateThunkValue {256 name: Option<IStr>,257 fctx: Pending<Context>,258 expr: LocExpr,259 }260 impl ThunkValue for EvaluateThunkValue {261 type Output = Val;262 fn get(self: Box<Self>) -> Result<Self::Output> {263 self.name.map_or_else(264 || evaluate(self.fctx.unwrap(), &self.expr),265 |name| evaluate_named(self.fctx.unwrap(), &self.expr, name),266 )267 }268 }269 let data = Thunk::new(tb!(EvaluateThunkValue {270 name: into.name(),271 fctx: fctx.clone(),272 expr: value.clone(),273 }));274 destruct(into, data, fctx, new_bindings)?;275 }276 BindSpec::Function {277 name,278 params,279 value,280 } => {281 #[derive(Trace)]282 struct MethodThunk {283 fctx: Pending<Context>,284 name: IStr,285 params: ParamsDesc,286 value: LocExpr,287 }288 impl ThunkValue for MethodThunk {289 type Output = Val;290291 fn get(self: Box<Self>) -> Result<Self::Output> {292 Ok(evaluate_method(293 self.fctx.unwrap(),294 self.name,295 self.params,296 self.value,297 ))298 }299 }300301 let old = new_bindings.insert(302 name.clone(),303 Thunk::new(tb!(MethodThunk {304 fctx,305 name: name.clone(),306 params: params.clone(),307 value: value.clone()308 })),309 );310 if old.is_some() {311 throw!(DuplicateLocalVar(name.clone()))312 }313 }314 }315 Ok(())316}