difftreelog
fix unify handling of field in obj and objcomp
in: master
3 files changed
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth1use std::{cmp::Ordering, rc::Rc};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_parser::{6 ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, FieldMember, FieldName, ForSpecData,7 IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc,8};9use jrsonnet_types::ValType;1011use crate::{12 destructure::evaluate_dest,13 error::Error::*,14 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},15 function::{CallLocation, FuncDesc, FuncVal},16 tb, throw,17 typed::Typed,18 val::{ArrValue, CachedUnbound, IndexableVal, Thunk, ThunkValue},19 Context, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, State,20 Unbound, Val,21};22pub mod destructure;23pub mod operator;2425pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {26 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {27 name,28 ctx,29 params,30 body,31 })))32}3334pub fn evaluate_field_name(ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {35 Ok(match field_name {36 FieldName::Fixed(n) => Some(n.clone()),37 FieldName::Dyn(expr) => State::push(38 CallLocation::new(&expr.1),39 || "evaluating field name".to_string(),40 || {41 let value = evaluate(ctx, expr)?;42 if matches!(value, Val::Null) {43 Ok(None)44 } else {45 Ok(Some(IStr::from_untyped(value)?))46 }47 },48 )?,49 })50}5152pub fn evaluate_comp(53 ctx: Context,54 specs: &[CompSpec],55 callback: &mut impl FnMut(Context) -> Result<()>,56) -> Result<()> {57 match specs.get(0) {58 None => callback(ctx)?,59 Some(CompSpec::IfSpec(IfSpecData(cond))) => {60 if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {61 evaluate_comp(ctx, &specs[1..], callback)?;62 }63 }64 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(ctx.clone(), expr)? {65 Val::Arr(list) => {66 for item in list.iter() {67 evaluate_comp(68 ctx.clone().with_var(var.clone(), item?.clone()),69 &specs[1..],70 callback,71 )?;72 }73 }74 _ => throw!(InComprehensionCanOnlyIterateOverArray),75 },76 }77 Ok(())78}7980trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}8182fn evaluate_object_locals(83 fctx: Pending<Context>,84 locals: Rc<Vec<BindSpec>>,85) -> impl CloneableUnbound<Context> {86 #[derive(Trace, Clone)]87 struct UnboundLocals {88 fctx: Pending<Context>,89 locals: Rc<Vec<BindSpec>>,90 }91 impl CloneableUnbound<Context> for UnboundLocals {}92 impl Unbound for UnboundLocals {93 type Bound = Context;9495 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Context> {96 let fctx = Context::new_future();97 let mut new_bindings = GcHashMap::new();98 for b in self.locals.iter() {99 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;100 }101102 let ctx = self.fctx.unwrap();103 let new_dollar = ctx.dollar().clone().or_else(|| this.clone());104105 let ctx = ctx106 .extend(new_bindings, new_dollar, sup, this)107 .into_future(fctx);108109 Ok(ctx)110 }111 }112113 UnboundLocals { fctx, locals }114}115116#[allow(clippy::too_many_lines)]117pub fn evaluate_member_list_object(ctx: Context, members: &[Member]) -> Result<ObjValue> {118 let mut builder = ObjValueBuilder::new();119 let locals = Rc::new(120 members121 .iter()122 .filter_map(|m| match m {123 Member::BindStmt(bind) => Some(bind.clone()),124 _ => None,125 })126 .collect::<Vec<_>>(),127 );128129 let fctx = Context::new_future();130131 // We have single context for all fields, so we can cache binds132 let uctx = CachedUnbound::new(evaluate_object_locals(fctx.clone(), locals));133134 for member in members.iter() {135 match member {136 Member::Field(FieldMember {137 name,138 plus,139 params: None,140 visibility,141 value,142 }) => {143 #[derive(Trace)]144 struct UnboundValue<B: Trace> {145 uctx: B,146 value: LocExpr,147 name: IStr,148 }149 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {150 type Bound = Val;151 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {152 Ok(evaluate_named(153 self.uctx.bind(sup, this)?,154 &self.value,155 self.name.clone(),156 )?)157 }158 }159160 let name = evaluate_field_name(ctx.clone(), name)?;161 let Some(name) = name else {162 continue;163 };164165 builder166 .member(name.clone())167 .with_add(*plus)168 .with_visibility(*visibility)169 .with_location(value.1.clone())170 .bindable(tb!(UnboundValue {171 uctx: uctx.clone(),172 value: value.clone(),173 name: name.clone()174 }))?;175 }176 Member::Field(FieldMember {177 name,178 params: Some(params),179 value,180 ..181 }) => {182 #[derive(Trace)]183 struct UnboundMethod<B: Trace> {184 uctx: B,185 value: LocExpr,186 params: ParamsDesc,187 name: IStr,188 }189 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {190 type Bound = Val;191 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {192 Ok(evaluate_method(193 self.uctx.bind(sup, this)?,194 self.name.clone(),195 self.params.clone(),196 self.value.clone(),197 ))198 }199 }200201 let Some(name) = evaluate_field_name(ctx.clone(), name)? else {202 continue;203 };204205 builder206 .member(name.clone())207 .hide()208 .with_location(value.1.clone())209 .bindable(tb!(UnboundMethod {210 uctx: uctx.clone(),211 value: value.clone(),212 params: params.clone(),213 name: name.clone()214 }))?;215 }216 Member::BindStmt(_) => {}217 Member::AssertStmt(stmt) => {218 #[derive(Trace)]219 struct ObjectAssert<B: Trace> {220 uctx: B,221 assert: AssertStmt,222 }223 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {224 fn run(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<()> {225 let ctx = self.uctx.bind(sup, this)?;226 evaluate_assert(ctx, &self.assert)227 }228 }229 builder.assert(tb!(ObjectAssert {230 uctx: uctx.clone(),231 assert: stmt.clone(),232 }));233 }234 }235 }236 let this = builder.build();237 fctx.fill(ctx.extend(GcHashMap::new(), None, None, Some(this.clone())));238 Ok(this)239}240241pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {242 Ok(match object {243 ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,244 ObjBody::ObjComp(obj) => {245 let mut builder = ObjValueBuilder::new();246 let locals = Rc::new(247 obj.pre_locals248 .iter()249 .chain(obj.post_locals.iter())250 .cloned()251 .collect::<Vec<_>>(),252 );253 let mut ctxs = vec![];254 evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {255 let key = evaluate(ctx.clone(), &obj.key)?;256 let fctx = Context::new_future();257 ctxs.push((ctx, fctx.clone()));258 let uctx = evaluate_object_locals(fctx, locals.clone());259260 match key {261 Val::Null => {}262 Val::Str(n) => {263 #[derive(Trace)]264 struct UnboundValue<B: Trace> {265 uctx: B,266 value: LocExpr,267 }268 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {269 type Bound = Val;270 fn bind(271 &self,272 sup: Option<ObjValue>,273 this: Option<ObjValue>,274 ) -> Result<Val> {275 Ok(evaluate(276 self.uctx.bind(sup, this.clone())?.extend(277 GcHashMap::new(),278 None,279 None,280 this,281 ),282 &self.value,283 )?)284 }285 }286 builder287 .member(n)288 .with_location(obj.value.1.clone())289 .with_add(obj.plus)290 .bindable(tb!(UnboundValue {291 uctx,292 value: obj.value.clone(),293 }))?;294 }295 v => throw!(FieldMustBeStringGot(v.value_type())),296 }297298 Ok(())299 })?;300301 let this = builder.build();302 for (ctx, fctx) in ctxs {303 let _ctx = ctx304 .extend(GcHashMap::new(), None, None, Some(this.clone()))305 .into_future(fctx);306 }307 this308 }309 })310}311312pub fn evaluate_apply(313 ctx: Context,314 value: &LocExpr,315 args: &ArgsDesc,316 loc: CallLocation<'_>,317 tailstrict: bool,318) -> Result<Val> {319 let value = evaluate(ctx.clone(), value)?;320 Ok(match value {321 Val::Func(f) => {322 let body = || f.evaluate(ctx, loc, args, tailstrict);323 if tailstrict {324 body()?325 } else {326 State::push(loc, || format!("function <{}> call", f.name()), body)?327 }328 }329 v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),330 })331}332333pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {334 let value = &assertion.0;335 let msg = &assertion.1;336 let assertion_result = State::push(337 CallLocation::new(&value.1),338 || "assertion condition".to_owned(),339 || bool::from_untyped(evaluate(ctx.clone(), value)?),340 )?;341 if !assertion_result {342 State::push(343 CallLocation::new(&value.1),344 || "assertion failure".to_owned(),345 || {346 if let Some(msg) = msg {347 throw!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));348 }349 throw!(AssertionFailed(Val::Null.to_string()?));350 },351 )?;352 }353 Ok(())354}355356pub fn evaluate_named(ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {357 use Expr::*;358 let LocExpr(raw_expr, _loc) = expr;359 Ok(match &**raw_expr {360 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),361 _ => evaluate(ctx, expr)?,362 })363}364365#[allow(clippy::too_many_lines)]366pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result<Val> {367 use Expr::*;368 let LocExpr(expr, loc) = expr;369 // let bp = with_state(|s| s.0.stop_at.borrow().clone());370 Ok(match &**expr {371 Literal(LiteralType::This) => {372 Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)373 }374 Literal(LiteralType::Super) => Val::Obj(375 ctx.super_obj().clone().ok_or(NoSuperFound)?.with_this(376 ctx.this()377 .clone()378 .expect("if super exists - then this should to"),379 ),380 ),381 Literal(LiteralType::Dollar) => {382 Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)383 }384 Literal(LiteralType::True) => Val::Bool(true),385 Literal(LiteralType::False) => Val::Bool(false),386 Literal(LiteralType::Null) => Val::Null,387 Parened(e) => evaluate(ctx, e)?,388 Str(v) => Val::Str(v.clone()),389 Num(v) => Val::new_checked_num(*v)?,390 BinaryOp(v1, o, v2) => evaluate_binary_op_special(ctx, v1, *o, v2)?,391 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,392 Var(name) => State::push(393 CallLocation::new(loc),394 || format!("variable <{name}> access"),395 || ctx.binding(name.clone())?.evaluate(),396 )?,397 Index(value, index) => match (evaluate(ctx.clone(), value)?, evaluate(ctx, index)?) {398 (Val::Obj(v), Val::Str(key)) => State::push(399 CallLocation::new(loc),400 || format!("field <{key}> access"),401 || match v.get(key.clone()) {402 Ok(Some(v)) => Ok(v),403 #[cfg(not(feature = "friendly-errors"))]404 Ok(None) => throw!(NoSuchField(key.clone(), vec![])),405 #[cfg(feature = "friendly-errors")]406 Ok(None) => {407 let mut heap = Vec::new();408 for field in v.fields_ex(409 true,410 #[cfg(feature = "exp-preserve-order")]411 false,412 ) {413 let conf = strsim::jaro_winkler(&field as &str, &key as &str);414 if conf < 0.8 {415 continue;416 }417 heap.push((conf, field));418 }419 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));420421 throw!(NoSuchField(422 key.clone(),423 heap.into_iter().map(|(_, v)| v).collect()424 ))425 }426 Err(e) => Err(e),427 },428 )?,429 (Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(430 ValType::Obj,431 ValType::Str,432 n.value_type(),433 )),434435 (Val::Arr(v), Val::Num(n)) => {436 if n.fract() > f64::EPSILON {437 throw!(FractionalIndex)438 }439 v.get(n as usize)?440 .ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?441 }442 (Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),443 (Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(444 ValType::Arr,445 ValType::Num,446 n.value_type(),447 )),448449 (Val::Str(s), Val::Num(n)) => Val::Str({450 let v: IStr = s451 .chars()452 .skip(n as usize)453 .take(1)454 .collect::<String>()455 .into();456 if v.is_empty() {457 let size = s.chars().count();458 throw!(StringBoundsError(n as usize, size))459 }460 v461 }),462 (Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(463 ValType::Str,464 ValType::Num,465 n.value_type(),466 )),467468 (v, _) => throw!(CantIndexInto(v.value_type())),469 },470 LocalExpr(bindings, returned) => {471 let mut new_bindings: GcHashMap<IStr, Thunk<Val>> =472 GcHashMap::with_capacity(bindings.len());473 let fctx = Context::new_future();474 for b in bindings {475 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;476 }477 let ctx = ctx.extend(new_bindings, None, None, None).into_future(fctx);478 evaluate(ctx, &returned.clone())?479 }480 Arr(items) => {481 let mut out = Vec::with_capacity(items.len());482 for item in items {483 // TODO: Implement ArrValue::Lazy with same context for every element?484 #[derive(Trace)]485 struct ArrayElement {486 ctx: Context,487 item: LocExpr,488 }489 impl ThunkValue for ArrayElement {490 type Output = Val;491 fn get(self: Box<Self>) -> Result<Val> {492 evaluate(self.ctx, &self.item)493 }494 }495 out.push(Thunk::new(tb!(ArrayElement {496 ctx: ctx.clone(),497 item: item.clone(),498 })));499 }500 Val::Arr(out.into())501 }502 ArrComp(expr, comp_specs) => {503 let mut out = Vec::new();504 evaluate_comp(ctx, comp_specs, &mut |ctx| {505 out.push(evaluate(ctx, expr)?);506 Ok(())507 })?;508 Val::Arr(ArrValue::Eager(Cc::new(out)))509 }510 Obj(body) => Val::Obj(evaluate_object(ctx, body)?),511 ObjExtend(a, b) => evaluate_add_op(512 &evaluate(ctx.clone(), a)?,513 &Val::Obj(evaluate_object(ctx, b)?),514 )?,515 Apply(value, args, tailstrict) => {516 evaluate_apply(ctx, value, args, CallLocation::new(loc), *tailstrict)?517 }518 Function(params, body) => {519 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())520 }521 AssertExpr(assert, returned) => {522 evaluate_assert(ctx.clone(), assert)?;523 evaluate(ctx, returned)?524 }525 ErrorStmt(e) => State::push(526 CallLocation::new(loc),527 || "error statement".to_owned(),528 || throw!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),529 )?,530 IfElse {531 cond,532 cond_then,533 cond_else,534 } => {535 if State::push(536 CallLocation::new(loc),537 || "if condition".to_owned(),538 || bool::from_untyped(evaluate(ctx.clone(), &cond.0)?),539 )? {540 evaluate(ctx, cond_then)?541 } else {542 match cond_else {543 Some(v) => evaluate(ctx, v)?,544 None => Val::Null,545 }546 }547 }548 Slice(value, desc) => {549 fn parse_idx<T: Typed>(550 loc: CallLocation<'_>,551 ctx: &Context,552 expr: &Option<LocExpr>,553 desc: &'static str,554 ) -> Result<Option<T>> {555 if let Some(value) = expr {556 Ok(Some(State::push(557 loc,558 || format!("slice {desc}"),559 || T::from_untyped(evaluate(ctx.clone(), value)?),560 )?))561 } else {562 Ok(None)563 }564 }565566 let indexable = evaluate(ctx.clone(), value)?;567 let loc = CallLocation::new(loc);568569 let start = parse_idx(loc, &ctx, &desc.start, "start")?;570 let end = parse_idx(loc, &ctx, &desc.end, "end")?;571 let step = parse_idx(loc, &ctx, &desc.step, "step")?;572573 IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?574 }575 i @ (Import(path) | ImportStr(path) | ImportBin(path)) => {576 let tmp = loc.clone().0;577 let s = ctx.state();578 let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?;579 match i {580 Import(_) => State::push(581 CallLocation::new(loc),582 || format!("import {:?}", path.clone()),583 || s.import_resolved(resolved_path),584 )?,585 ImportStr(_) => Val::Str(s.import_resolved_str(resolved_path)?),586 ImportBin(_) => Val::Arr(ArrValue::Bytes(s.import_resolved_bin(resolved_path)?)),587 _ => unreachable!(),588 }589 }590 })591}1use std::{cmp::Ordering, rc::Rc};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_parser::{6 ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, FieldMember, FieldName, ForSpecData,7 IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc,8};9use jrsonnet_types::ValType;1011use crate::{12 destructure::evaluate_dest,13 error::Error::*,14 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},15 function::{CallLocation, FuncDesc, FuncVal},16 tb, throw,17 typed::Typed,18 val::{ArrValue, CachedUnbound, IndexableVal, Thunk, ThunkValue},19 Context, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, State,20 Unbound, Val,21};22pub mod destructure;23pub mod operator;2425pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {26 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {27 name,28 ctx,29 params,30 body,31 })))32}3334pub fn evaluate_field_name(ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {35 Ok(match field_name {36 FieldName::Fixed(n) => Some(n.clone()),37 FieldName::Dyn(expr) => State::push(38 CallLocation::new(&expr.1),39 || "evaluating field name".to_string(),40 || {41 let value = evaluate(ctx, expr)?;42 if matches!(value, Val::Null) {43 Ok(None)44 } else {45 Ok(Some(IStr::from_untyped(value)?))46 }47 },48 )?,49 })50}5152pub fn evaluate_comp(53 ctx: Context,54 specs: &[CompSpec],55 callback: &mut impl FnMut(Context) -> Result<()>,56) -> Result<()> {57 match specs.get(0) {58 None => callback(ctx)?,59 Some(CompSpec::IfSpec(IfSpecData(cond))) => {60 if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {61 evaluate_comp(ctx, &specs[1..], callback)?;62 }63 }64 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(ctx.clone(), expr)? {65 Val::Arr(list) => {66 for item in list.iter() {67 evaluate_comp(68 ctx.clone().with_var(var.clone(), item?.clone()),69 &specs[1..],70 callback,71 )?;72 }73 }74 _ => throw!(InComprehensionCanOnlyIterateOverArray),75 },76 }77 Ok(())78}7980trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}8182fn evaluate_object_locals(83 fctx: Pending<Context>,84 locals: Rc<Vec<BindSpec>>,85) -> impl CloneableUnbound<Context> {86 #[derive(Trace, Clone)]87 struct UnboundLocals {88 fctx: Pending<Context>,89 locals: Rc<Vec<BindSpec>>,90 }91 impl CloneableUnbound<Context> for UnboundLocals {}92 impl Unbound for UnboundLocals {93 type Bound = Context;9495 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Context> {96 let fctx = Context::new_future();97 let mut new_bindings = GcHashMap::new();98 for b in self.locals.iter() {99 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;100 }101102 let ctx = self.fctx.unwrap();103 let new_dollar = ctx.dollar().clone().or_else(|| this.clone());104105 let ctx = ctx106 .extend(new_bindings, new_dollar, sup, this)107 .into_future(fctx);108109 Ok(ctx)110 }111 }112113 UnboundLocals { fctx, locals }114}115116pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(117 builder: &mut ObjValueBuilder,118 ctx: Context,119 uctx: B,120 member: &FieldMember,121) -> Result<()> {122 match member {123 FieldMember {124 name,125 plus,126 params: None,127 visibility,128 value,129 } => {130 #[derive(Trace)]131 struct UnboundValue<B: Trace> {132 uctx: B,133 value: LocExpr,134 name: IStr,135 }136 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {137 type Bound = Val;138 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {139 Ok(evaluate_named(140 self.uctx.bind(sup, this)?,141 &self.value,142 self.name.clone(),143 )?)144 }145 }146147 let name = evaluate_field_name(ctx.clone(), name)?;148 let Some(name) = name else {149 return Ok(());150 };151152 builder153 .member(name.clone())154 .with_add(*plus)155 .with_visibility(*visibility)156 .with_location(value.1.clone())157 .bindable(tb!(UnboundValue {158 uctx: uctx.clone(),159 value: value.clone(),160 name: name.clone()161 }))?;162 }163 FieldMember {164 name,165 params: Some(params),166 value,167 ..168 } => {169 #[derive(Trace)]170 struct UnboundMethod<B: Trace> {171 uctx: B,172 value: LocExpr,173 params: ParamsDesc,174 name: IStr,175 }176 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {177 type Bound = Val;178 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {179 Ok(evaluate_method(180 self.uctx.bind(sup, this)?,181 self.name.clone(),182 self.params.clone(),183 self.value.clone(),184 ))185 }186 }187188 let Some(name) = evaluate_field_name(ctx.clone(), name)? else {189 return Ok(());190 };191192 builder193 .member(name.clone())194 .hide()195 .with_location(value.1.clone())196 .bindable(tb!(UnboundMethod {197 uctx: uctx.clone(),198 value: value.clone(),199 params: params.clone(),200 name: name.clone()201 }))?;202 }203 }204 Ok(())205}206207#[allow(clippy::too_many_lines)]208pub fn evaluate_member_list_object(ctx: Context, members: &[Member]) -> Result<ObjValue> {209 let mut builder = ObjValueBuilder::new();210 let locals = Rc::new(211 members212 .iter()213 .filter_map(|m| match m {214 Member::BindStmt(bind) => Some(bind.clone()),215 _ => None,216 })217 .collect::<Vec<_>>(),218 );219220 let fctx = Context::new_future();221222 // We have single context for all fields, so we can cache binds223 let uctx = CachedUnbound::new(evaluate_object_locals(fctx.clone(), locals));224225 for member in members.iter() {226 match member {227 Member::Field(field) => {228 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), &field)?229 }230 Member::BindStmt(_) => {231 // Already handled232 }233 Member::AssertStmt(stmt) => {234 #[derive(Trace)]235 struct ObjectAssert<B: Trace> {236 uctx: B,237 assert: AssertStmt,238 }239 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {240 fn run(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<()> {241 let ctx = self.uctx.bind(sup, this)?;242 evaluate_assert(ctx, &self.assert)243 }244 }245 builder.assert(tb!(ObjectAssert {246 uctx: uctx.clone(),247 assert: stmt.clone(),248 }));249 }250 }251 }252 let this = builder.build();253 fctx.fill(ctx.extend(GcHashMap::new(), None, None, Some(this.clone())));254 Ok(this)255}256257pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {258 Ok(match object {259 ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,260 ObjBody::ObjComp(obj) => {261 let mut builder = ObjValueBuilder::new();262 let locals = Rc::new(263 obj.pre_locals264 .iter()265 .chain(obj.post_locals.iter())266 .cloned()267 .collect::<Vec<_>>(),268 );269 let mut ctxs = vec![];270 evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {271 let fctx = Context::new_future();272 ctxs.push((ctx.clone(), fctx.clone()));273 let uctx = evaluate_object_locals(fctx, locals.clone());274275 evaluate_field_member(&mut builder, ctx, uctx, &obj.field)276 })?;277278 let this = builder.build();279 for (ctx, fctx) in ctxs {280 let _ctx = ctx281 .extend(GcHashMap::new(), None, None, Some(this.clone()))282 .into_future(fctx);283 }284 this285 }286 })287}288289pub fn evaluate_apply(290 ctx: Context,291 value: &LocExpr,292 args: &ArgsDesc,293 loc: CallLocation<'_>,294 tailstrict: bool,295) -> Result<Val> {296 let value = evaluate(ctx.clone(), value)?;297 Ok(match value {298 Val::Func(f) => {299 let body = || f.evaluate(ctx, loc, args, tailstrict);300 if tailstrict {301 body()?302 } else {303 State::push(loc, || format!("function <{}> call", f.name()), body)?304 }305 }306 v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),307 })308}309310pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {311 let value = &assertion.0;312 let msg = &assertion.1;313 let assertion_result = State::push(314 CallLocation::new(&value.1),315 || "assertion condition".to_owned(),316 || bool::from_untyped(evaluate(ctx.clone(), value)?),317 )?;318 if !assertion_result {319 State::push(320 CallLocation::new(&value.1),321 || "assertion failure".to_owned(),322 || {323 if let Some(msg) = msg {324 throw!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));325 }326 throw!(AssertionFailed(Val::Null.to_string()?));327 },328 )?;329 }330 Ok(())331}332333pub fn evaluate_named(ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {334 use Expr::*;335 let LocExpr(raw_expr, _loc) = expr;336 Ok(match &**raw_expr {337 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),338 _ => evaluate(ctx, expr)?,339 })340}341342#[allow(clippy::too_many_lines)]343pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result<Val> {344 use Expr::*;345 let LocExpr(expr, loc) = expr;346 // let bp = with_state(|s| s.0.stop_at.borrow().clone());347 Ok(match &**expr {348 Literal(LiteralType::This) => {349 Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)350 }351 Literal(LiteralType::Super) => Val::Obj(352 ctx.super_obj().clone().ok_or(NoSuperFound)?.with_this(353 ctx.this()354 .clone()355 .expect("if super exists - then this should to"),356 ),357 ),358 Literal(LiteralType::Dollar) => {359 Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)360 }361 Literal(LiteralType::True) => Val::Bool(true),362 Literal(LiteralType::False) => Val::Bool(false),363 Literal(LiteralType::Null) => Val::Null,364 Parened(e) => evaluate(ctx, e)?,365 Str(v) => Val::Str(v.clone()),366 Num(v) => Val::new_checked_num(*v)?,367 BinaryOp(v1, o, v2) => evaluate_binary_op_special(ctx, v1, *o, v2)?,368 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,369 Var(name) => State::push(370 CallLocation::new(loc),371 || format!("variable <{name}> access"),372 || ctx.binding(name.clone())?.evaluate(),373 )?,374 Index(value, index) => match (evaluate(ctx.clone(), value)?, evaluate(ctx, index)?) {375 (Val::Obj(v), Val::Str(key)) => State::push(376 CallLocation::new(loc),377 || format!("field <{key}> access"),378 || match v.get(key.clone()) {379 Ok(Some(v)) => Ok(v),380 #[cfg(not(feature = "friendly-errors"))]381 Ok(None) => throw!(NoSuchField(key.clone(), vec![])),382 #[cfg(feature = "friendly-errors")]383 Ok(None) => {384 let mut heap = Vec::new();385 for field in v.fields_ex(386 true,387 #[cfg(feature = "exp-preserve-order")]388 false,389 ) {390 let conf = strsim::jaro_winkler(&field as &str, &key as &str);391 if conf < 0.8 {392 continue;393 }394 heap.push((conf, field));395 }396 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));397398 throw!(NoSuchField(399 key.clone(),400 heap.into_iter().map(|(_, v)| v).collect()401 ))402 }403 Err(e) => Err(e),404 },405 )?,406 (Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(407 ValType::Obj,408 ValType::Str,409 n.value_type(),410 )),411412 (Val::Arr(v), Val::Num(n)) => {413 if n.fract() > f64::EPSILON {414 throw!(FractionalIndex)415 }416 v.get(n as usize)?417 .ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?418 }419 (Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),420 (Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(421 ValType::Arr,422 ValType::Num,423 n.value_type(),424 )),425426 (Val::Str(s), Val::Num(n)) => Val::Str({427 let v: IStr = s428 .chars()429 .skip(n as usize)430 .take(1)431 .collect::<String>()432 .into();433 if v.is_empty() {434 let size = s.chars().count();435 throw!(StringBoundsError(n as usize, size))436 }437 v438 }),439 (Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(440 ValType::Str,441 ValType::Num,442 n.value_type(),443 )),444445 (v, _) => throw!(CantIndexInto(v.value_type())),446 },447 LocalExpr(bindings, returned) => {448 let mut new_bindings: GcHashMap<IStr, Thunk<Val>> =449 GcHashMap::with_capacity(bindings.len());450 let fctx = Context::new_future();451 for b in bindings {452 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;453 }454 let ctx = ctx.extend(new_bindings, None, None, None).into_future(fctx);455 evaluate(ctx, &returned.clone())?456 }457 Arr(items) => {458 let mut out = Vec::with_capacity(items.len());459 for item in items {460 // TODO: Implement ArrValue::Lazy with same context for every element?461 #[derive(Trace)]462 struct ArrayElement {463 ctx: Context,464 item: LocExpr,465 }466 impl ThunkValue for ArrayElement {467 type Output = Val;468 fn get(self: Box<Self>) -> Result<Val> {469 evaluate(self.ctx, &self.item)470 }471 }472 out.push(Thunk::new(tb!(ArrayElement {473 ctx: ctx.clone(),474 item: item.clone(),475 })));476 }477 Val::Arr(out.into())478 }479 ArrComp(expr, comp_specs) => {480 let mut out = Vec::new();481 evaluate_comp(ctx, comp_specs, &mut |ctx| {482 out.push(evaluate(ctx, expr)?);483 Ok(())484 })?;485 Val::Arr(ArrValue::Eager(Cc::new(out)))486 }487 Obj(body) => Val::Obj(evaluate_object(ctx, body)?),488 ObjExtend(a, b) => evaluate_add_op(489 &evaluate(ctx.clone(), a)?,490 &Val::Obj(evaluate_object(ctx, b)?),491 )?,492 Apply(value, args, tailstrict) => {493 evaluate_apply(ctx, value, args, CallLocation::new(loc), *tailstrict)?494 }495 Function(params, body) => {496 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())497 }498 AssertExpr(assert, returned) => {499 evaluate_assert(ctx.clone(), assert)?;500 evaluate(ctx, returned)?501 }502 ErrorStmt(e) => State::push(503 CallLocation::new(loc),504 || "error statement".to_owned(),505 || throw!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),506 )?,507 IfElse {508 cond,509 cond_then,510 cond_else,511 } => {512 if State::push(513 CallLocation::new(loc),514 || "if condition".to_owned(),515 || bool::from_untyped(evaluate(ctx.clone(), &cond.0)?),516 )? {517 evaluate(ctx, cond_then)?518 } else {519 match cond_else {520 Some(v) => evaluate(ctx, v)?,521 None => Val::Null,522 }523 }524 }525 Slice(value, desc) => {526 fn parse_idx<T: Typed>(527 loc: CallLocation<'_>,528 ctx: &Context,529 expr: &Option<LocExpr>,530 desc: &'static str,531 ) -> Result<Option<T>> {532 if let Some(value) = expr {533 Ok(Some(State::push(534 loc,535 || format!("slice {desc}"),536 || T::from_untyped(evaluate(ctx.clone(), value)?),537 )?))538 } else {539 Ok(None)540 }541 }542543 let indexable = evaluate(ctx.clone(), value)?;544 let loc = CallLocation::new(loc);545546 let start = parse_idx(loc, &ctx, &desc.start, "start")?;547 let end = parse_idx(loc, &ctx, &desc.end, "end")?;548 let step = parse_idx(loc, &ctx, &desc.step, "step")?;549550 IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?551 }552 i @ (Import(path) | ImportStr(path) | ImportBin(path)) => {553 let tmp = loc.clone().0;554 let s = ctx.state();555 let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?;556 match i {557 Import(_) => State::push(558 CallLocation::new(loc),559 || format!("import {:?}", path.clone()),560 || s.import_resolved(resolved_path),561 )?,562 ImportStr(_) => Val::Str(s.import_resolved_str(resolved_path)?),563 ImportBin(_) => Val::Arr(ArrValue::Bytes(s.import_resolved_bin(resolved_path)?)),564 _ => unreachable!(),565 }566 }567 })568}crates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -268,9 +268,7 @@
#[derive(Debug, PartialEq, Trace)]
pub struct ObjComp {
pub pre_locals: Vec<BindSpec>,
- pub key: LocExpr,
- pub plus: bool,
- pub value: LocExpr,
+ pub field: FieldMember,
pub post_locals: Vec<BindSpec>,
pub compspecs: Vec<CompSpec>,
}
crates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -194,14 +194,12 @@
/ assertion:assertion(s) {expr::Member::AssertStmt(assertion)}
/ field:field(s) {expr::Member::Field(field)}
pub rule objinside(s: &ParserSettings) -> expr::ObjBody
- = pre_locals:(b: obj_local(s) comma() {b})* "[" _ key:expr(s) _ "]" _ plus:"+"? _ ":" _ value:expr(s) post_locals:(comma() b:obj_local(s) {b})* _ ("," _)? forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {
+ = pre_locals:(b: obj_local(s) comma() {b})* field:field(s) post_locals:(comma() b:obj_local(s) {b})* _ ("," _)? forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {
let mut compspecs = vec![CompSpec::ForSpec(forspec)];
compspecs.extend(others.unwrap_or_default());
expr::ObjBody::ObjComp(expr::ObjComp{
pre_locals,
- key,
- plus: plus.is_some(),
- value,
+ field,
post_locals,
compspecs,
})