1use gcmodule::Trace;2use jrsonnet_interner::IStr;3use jrsonnet_parser::{BindSpec, Destruct, LocExpr, ParamsDesc};45use crate::{6 error::{Error::*, Result},7 evaluate, evaluate_method,8 gc::GcHashMap,9 tb, throw,10 val::ThunkValue,11 Context, Pending, State, Thunk, Val,12};1314fn destruct(15 d: &Destruct,16 parent: Thunk<Val>,17 new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,18) -> Result<()> {19 Ok(match d {20 Destruct::Full(v) => {21 let old = new_bindings.insert(v.clone(), parent);22 if old.is_some() {23 throw!(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 use crate::{throw_runtime, val::ArrValue};3334 #[derive(Trace)]35 struct DataThunk {36 parent: Thunk<Val>,37 min_len: usize,38 has_rest: bool,39 }40 impl ThunkValue for DataThunk {41 type Output = ArrValue;4243 fn get(self: Box<Self>, s: State) -> Result<Self::Output> {44 let v = self.parent.evaluate(s)?;45 let arr = match v {46 Val::Arr(a) => a,47 _ => throw_runtime!("expected array"),48 };49 if !self.has_rest {50 if arr.len() != self.min_len {51 throw_runtime!("expected {} elements, got {}", self.min_len, arr.len())52 }53 } else if arr.len() < self.min_len {54 throw_runtime!(55 "expected at least {} elements, but array was only {}",56 self.min_len,57 arr.len()58 )59 }60 Ok(arr)61 }62 }6364 let full = Thunk::new(tb!(DataThunk {65 min_len: start.len() + end.len(),66 has_rest: rest.is_some(),67 parent,68 }));6970 {71 #[derive(Trace)]72 struct BaseThunk {73 full: Thunk<ArrValue>,74 index: usize,75 }76 impl ThunkValue for BaseThunk {77 type Output = Val;7879 fn get(self: Box<Self>, s: State) -> Result<Self::Output> {80 let full = self.full.evaluate(s.clone())?;81 Ok(full.get(s, self.index)?.expect("length is checked"))82 }83 }84 for (i, d) in start.iter().enumerate() {85 destruct(86 d,87 Thunk::new(tb!(BaseThunk {88 full: full.clone(),89 index: i,90 })),91 new_bindings,92 )?;93 }94 }9596 match rest {97 Some(DestructRest::Keep(v)) => {98 #[derive(Trace)]99 struct RestThunk {100 full: Thunk<ArrValue>,101 start: usize,102 end: usize,103 }104 impl ThunkValue for RestThunk {105 type Output = Val;106107 fn get(self: Box<Self>, s: State) -> Result<Self::Output> {108 let full = self.full.evaluate(s)?;109 let to = full.len() - self.end;110 Ok(Val::Arr(full.slice(Some(self.start), Some(to), None)))111 }112 }113114 destruct(115 &Destruct::Full(v.clone()),116 Thunk::new(tb!(RestThunk {117 full: full.clone(),118 start: start.len(),119 end: end.len(),120 })),121 new_bindings,122 )?;123 }124 Some(DestructRest::Drop) => {}125 None => {}126 }127128 {129 #[derive(Trace)]130 struct EndThunk {131 full: Thunk<ArrValue>,132 index: usize,133 end: usize,134 }135 impl ThunkValue for EndThunk {136 type Output = Val;137138 fn get(self: Box<Self>, s: State) -> Result<Self::Output> {139 let full = self.full.evaluate(s.clone())?;140 Ok(full141 .get(s, full.len() - self.end + self.index)?142 .expect("length is checked"))143 }144 }145 for (i, d) in end.iter().enumerate() {146 destruct(147 d,148 Thunk::new(tb!(EndThunk {149 full: full.clone(),150 index: i,151 end: end.len(),152 })),153 new_bindings,154 )?;155 }156 }157 }158 #[cfg(feature = "exp-destruct")]159 Destruct::Object { fields, rest } => {160 use jrsonnet_parser::DestructRest;161162 use crate::{obj::ObjValue, throw_runtime};163164 #[derive(Trace)]165 struct DataThunk {166 parent: Thunk<Val>,167 field_names: Vec<IStr>,168 has_rest: bool,169 }170 impl ThunkValue for DataThunk {171 type Output = ObjValue;172173 fn get(self: Box<Self>, s: State) -> Result<Self::Output> {174 let v = self.parent.evaluate(s)?;175 let obj = match v {176 Val::Obj(o) => o,177 _ => throw_runtime!("expected object"),178 };179 for field in &self.field_names {180 if !obj.has_field_ex(field.clone(), true) {181 throw_runtime!("missing field: {}", field);182 }183 }184 if !self.has_rest {185 let len = obj.len();186 if len != self.field_names.len() {187 throw_runtime!("too many fields, and rest not found");188 }189 }190 Ok(obj)191 }192 }193 let field_names: Vec<_> = fields.iter().map(|f| f.0.clone()).collect();194 let full = Thunk::new(tb!(DataThunk {195 parent,196 field_names: field_names.clone(),197 has_rest: rest.is_some()198 }));199200 for (field, d) in fields {201 #[derive(Trace)]202 struct FieldThunk {203 full: Thunk<ObjValue>,204 field: IStr,205 }206 impl ThunkValue for FieldThunk {207 type Output = Val;208209 fn get(self: Box<Self>, s: State) -> Result<Self::Output> {210 let full = self.full.evaluate(s.clone())?;211 let field = full.get(s, self.field)?.expect("shape is checked");212 Ok(field)213 }214 }215 let value = Thunk::new(tb!(FieldThunk {216 full: full.clone(),217 field: field.clone()218 }));219 if let Some(d) = d {220 destruct(d, value, new_bindings)?;221 } else {222 destruct(&Destruct::Full(field.clone()), value, new_bindings)?;223 }224 }225 }226 })227}228229pub fn evaluate_dest(230 d: &BindSpec,231 fctx: Pending<Context>,232 new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,233) -> Result<()> {234 match d {235 BindSpec::Field { into, value } => {236 #[derive(Trace)]237 struct EvaluateThunkValue {238 fctx: Pending<Context>,239 expr: LocExpr,240 }241 impl ThunkValue for EvaluateThunkValue {242 type Output = Val;243 fn get(self: Box<Self>, s: State) -> Result<Self::Output> {244 evaluate(s, self.fctx.unwrap(), &self.expr)245 }246 }247 248 let data = Thunk::new(tb!(EvaluateThunkValue {249 fctx,250 expr: value.clone(),251 }));252 destruct(into, data, new_bindings)?;253 }254 BindSpec::Function {255 name,256 params,257 value,258 } => {259 #[derive(Trace)]260 struct MethodThunk {261 fctx: Pending<Context>,262 name: IStr,263 params: ParamsDesc,264 value: LocExpr,265 }266 impl ThunkValue for MethodThunk {267 type Output = Val;268269 fn get(self: Box<Self>, _s: State) -> Result<Self::Output> {270 Ok(evaluate_method(271 self.fctx.unwrap(),272 self.name,273 self.params,274 self.value,275 ))276 }277 }278279 let old = new_bindings.insert(280 name.clone(),281 Thunk::new(tb!(MethodThunk {282 fctx,283 name: name.clone(),284 params: params.clone(),285 value: value.clone()286 })),287 );288 if old.is_some() {289 throw!(DuplicateLocalVar(name.clone()))290 }291 }292 }293 Ok(())294}