difftreelog
refactor(ir) flatten obj member
in: master
7 files changed
crates/jrsonnet-evaluator/src/async_import.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/async_import.rs
+++ b/crates/jrsonnet-evaluator/src/async_import.rs
@@ -4,7 +4,7 @@
use jrsonnet_gcmodule::Acyclic;
use jrsonnet_parser::{
ArgsDesc, AssertExpr, AssertStmt, BindSpec, CompSpec, Destruct, Expr, FieldMember, FieldName,
- ForSpecData, IfElse, IfSpecData, ImportKind, Member, ObjBody, Param, ParamsDesc,
+ ForSpecData, IfElse, IfSpecData, ImportKind, ObjBody, Param, ParamsDesc,
ParserSettings, Slice, SliceDesc, Source, SourcePath, Spanned,
};
use rustc_hash::FxHashMap;
@@ -102,31 +102,30 @@
}
fn in_obj(obj: &ObjBody, out: &mut FoundImports) {
match obj {
- ObjBody::MemberList(v) => {
- for member in v {
- match member {
- Member::Field(FieldMember {
- name,
- params,
- value,
- ..
- }) => {
- match name {
- FieldName::Fixed(_) => {}
- FieldName::Dyn(expr) => find_imports(expr, out),
- }
- if let Some(params) = params {
- in_params(params, out);
- }
- find_imports(value, out);
- }
- Member::BindStmt(_) => todo!(),
- Member::AssertStmt(assert) => {
- find_imports(&assert.0, out);
- if let Some(expr) = &assert.1 {
- find_imports(expr, out);
- }
- }
+ ObjBody::MemberList(obj) => {
+ for FieldMember {
+ name,
+ params,
+ value,
+ ..
+ } in &obj.fields
+ {
+ match name {
+ FieldName::Fixed(_) => {}
+ FieldName::Dyn(expr) => find_imports(expr, out),
+ }
+ if let Some(params) = params {
+ in_params(params, out);
+ }
+ find_imports(value, out);
+ }
+ for _ in &*obj.locals {
+ todo!()
+ }
+ for assert in &*obj.asserts {
+ find_imports(&assert.0, out);
+ if let Some(expr) = &assert.1 {
+ find_imports(expr, out);
}
}
}
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth1use std::rc::Rc;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_parser::{6 ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, FieldMember, FieldName,7 ForSpecData, IfSpecData, ImportKind, LiteralType, ObjBody, ObjMembers, ParamsDesc, Spanned,8};9use jrsonnet_types::ValType;10use rustc_hash::FxHashMap;1112use self::destructure::destruct;13use crate::{14 arr::ArrValue,15 bail,16 destructure::evaluate_dest,17 error::{suggest_object_fields, ErrorKind::*},18 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},19 function::{CallLocation, FuncDesc, FuncVal},20 gc::WithCapacityExt as _,21 in_frame,22 typed::Typed,23 val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk},24 with_state, Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,25 ResultExt, SupThis, Unbound, Val,26};27pub mod destructure;28pub mod operator;2930// This is the amount of bytes that need to be left on the stack before increasing the size.31// It must be at least as large as the stack required by any code that does not call32// `ensure_sufficient_stack`.33const RED_ZONE: usize = 100 * 1024; // 100k3435// Only the first stack that is pushed, grows exponentially (2^n * STACK_PER_RECURSION) from then36// on. This flag has performance relevant characteristics. Don't set it too high.37const STACK_PER_RECURSION: usize = 1024 * 1024; // 1MB3839/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations40/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit41/// from this.42///43/// Should not be sprinkled around carelessly, as it causes a little bit of overhead.44#[inline]45pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {46 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)47}4849pub fn evaluate_trivial(expr: &Spanned<Expr>) -> Option<Val> {50 fn is_trivial(expr: &Spanned<Expr>) -> bool {51 match &**expr {52 Expr::Str(_)53 | Expr::Num(_)54 | Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true,55 Expr::Arr(a) => a.iter().all(is_trivial),56 _ => false,57 }58 }59 Some(match &**expr {60 Expr::Str(s) => Val::string(s.clone()),61 Expr::Num(n) => {62 Val::Num(NumValue::new(*n).expect("parser will not allow non-finite values"))63 }64 Expr::Literal(LiteralType::False) => Val::Bool(false),65 Expr::Literal(LiteralType::True) => Val::Bool(true),66 Expr::Literal(LiteralType::Null) => Val::Null,67 Expr::Arr(n) => {68 if n.iter().any(|e| !is_trivial(e)) {69 return None;70 }71 Val::Arr(ArrValue::eager(72 n.iter()73 .map(evaluate_trivial)74 .map(|e| e.expect("checked trivial"))75 .collect(),76 ))77 }78 _ => return None,79 })80}8182pub fn evaluate_method(83 ctx: Context,84 name: IStr,85 params: ParamsDesc,86 body: Rc<Spanned<Expr>>,87) -> Val {88 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {89 name,90 ctx,91 params,92 body,93 })))94}9596pub fn evaluate_field_name(ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {97 Ok(match field_name {98 FieldName::Fixed(n) => Some(n.clone()),99 FieldName::Dyn(expr) => in_frame(100 CallLocation::new(&expr.span()),101 || "evaluating field name".to_string(),102 || {103 let value = evaluate(ctx, expr)?;104 if matches!(value, Val::Null) {105 Ok(None)106 } else {107 Ok(Some(IStr::from_untyped(value)?))108 }109 },110 )?,111 })112}113114pub fn evaluate_comp(115 ctx: Context,116 specs: &[CompSpec],117 callback: &mut impl FnMut(Context) -> Result<()>,118) -> Result<()> {119 match specs.first() {120 None => callback(ctx)?,121 Some(CompSpec::IfSpec(IfSpecData(cond))) => {122 if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {123 evaluate_comp(ctx, &specs[1..], callback)?;124 }125 }126 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(ctx.clone(), expr)? {127 Val::Arr(list) => {128 for item in list.iter_lazy() {129 let fctx = Pending::new();130 let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());131 destruct(var, item, fctx.clone(), &mut new_bindings)?;132 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);133134 evaluate_comp(ctx, &specs[1..], callback)?;135 }136 }137 #[cfg(feature = "exp-object-iteration")]138 Val::Obj(obj) => {139 for field in obj.fields(140 // TODO: Should there be ability to preserve iteration order?141 #[cfg(feature = "exp-preserve-order")]142 false,143 ) {144 let fctx = Pending::new();145 let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());146 let obj = obj.clone();147 let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![148 Thunk::evaluated(Val::string(field.clone())),149 Thunk!(move || obj.get(field).transpose().expect(150 "field exists, as field name was obtained from object.fields()",151 )),152 ])));153 destruct(var, value, fctx.clone(), &mut new_bindings)?;154 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);155156 evaluate_comp(ctx, &specs[1..], callback)?;157 }158 }159 _ => bail!(InComprehensionCanOnlyIterateOverArray),160 },161 }162 Ok(())163}164165trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}166impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}167168fn evaluate_object_locals(169 fctx: Context,170 locals: Rc<Vec<BindSpec>>,171) -> impl CloneableUnbound<Context> {172 #[derive(Trace, Clone)]173 struct UnboundLocals {174 fctx: Context,175 locals: Rc<Vec<BindSpec>>,176 }177 impl Unbound for UnboundLocals {178 type Bound = Context;179180 fn bind(&self, sup_this: SupThis) -> Result<Context> {181 let fctx = Context::new_future();182 let mut new_bindings =183 FxHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum());184 for b in self.locals.iter() {185 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;186 }187188 let ctx = self.fctx.clone();189190 let ctx = ctx191 .extend_bindings_sup_this(new_bindings, sup_this)192 .into_future(fctx);193194 Ok(ctx)195 }196 }197198 UnboundLocals { fctx, locals }199}200201pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(202 builder: &mut ObjValueBuilder,203 ctx: Context,204 uctx: B,205 field: &FieldMember,206) -> Result<()> {207 let name = evaluate_field_name(ctx, &field.name)?;208 let Some(name) = name else {209 return Ok(());210 };211212 match field {213 FieldMember {214 plus,215 params: None,216 visibility,217 value,218 ..219 } => {220 #[derive(Trace)]221 struct UnboundValue<B: Trace> {222 uctx: B,223 value: Rc<Spanned<Expr>>,224 name: IStr,225 }226 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {227 type Bound = Val;228 fn bind(&self, sup_this: SupThis) -> Result<Val> {229 evaluate_named(self.uctx.bind(sup_this)?, &self.value, self.name.clone())230 }231 }232233 builder234 .field(name.clone())235 .with_add(*plus)236 .with_visibility(*visibility)237 .with_location(value.span())238 .bindable(UnboundValue {239 uctx,240 value: value.clone(),241 name,242 })?;243 }244 FieldMember {245 params: Some(params),246 visibility,247 value,248 ..249 } => {250 #[derive(Trace)]251 struct UnboundMethod<B: Trace> {252 uctx: B,253 value: Rc<Spanned<Expr>>,254 params: ParamsDesc,255 name: IStr,256 }257 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {258 type Bound = Val;259 fn bind(&self, sup_this: SupThis) -> Result<Val> {260 Ok(evaluate_method(261 self.uctx.bind(sup_this)?,262 self.name.clone(),263 self.params.clone(),264 self.value.clone(),265 ))266 }267 }268269 builder270 .field(name.clone())271 .with_visibility(*visibility)272 .with_location(value.span())273 .bindable(UnboundMethod {274 uctx,275 value: value.clone(),276 params: params.clone(),277 name,278 })?;279 }280 }281 Ok(())282}283284#[allow(clippy::too_many_lines)]285pub fn evaluate_member_list_object(ctx: Context, members: &ObjMembers) -> Result<ObjValue> {286 let mut builder = ObjValueBuilder::new();287 let locals = members.locals.clone();288289 // We have single context for all fields, so we can cache binds290 let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals));291292 for field in &members.fields {293 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), &field)?;294 }295296 if !members.asserts.is_empty() {297 #[derive(Trace)]298 struct ObjectAssert<B: Trace> {299 uctx: B,300 asserts: Rc<Vec<AssertStmt>>,301 }302 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {303 fn run(&self, sup_this: SupThis) -> Result<()> {304 let ctx = self.uctx.bind(sup_this)?;305 for assert in &*self.asserts {306 evaluate_assert(ctx.clone(), &assert)?;307 }308 Ok(())309 }310 }311 builder.assert(ObjectAssert {312 uctx: uctx.clone(),313 asserts: members.asserts.clone(),314 });315 }316317 Ok(builder.build())318}319320pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {321 Ok(match object {322 ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,323 ObjBody::ObjComp(obj) => {324 let mut builder = ObjValueBuilder::new();325 let locals = obj.locals.clone();326 evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {327 let uctx = evaluate_object_locals(ctx.clone(), locals.clone());328329 evaluate_field_member(&mut builder, ctx, uctx, &obj.field)330 })?;331332 builder.build()333 }334 })335}336337pub fn evaluate_apply(338 ctx: Context,339 value: &Spanned<Expr>,340 args: &ArgsDesc,341 loc: CallLocation<'_>,342 tailstrict: bool,343) -> Result<Val> {344 let value = evaluate(ctx.clone(), value)?;345 Ok(match value {346 Val::Func(f) => {347 let body = || f.evaluate(ctx, loc, args, tailstrict);348 if tailstrict {349 body()?350 } else {351 in_frame(loc, || format!("function <{}> call", f.name()), body)?352 }353 }354 v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())),355 })356}357358pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {359 let value = &assertion.0;360 let msg = &assertion.1;361 let assertion_result = in_frame(362 CallLocation::new(&value.span()),363 || "assertion condition".to_owned(),364 || bool::from_untyped(evaluate(ctx.clone(), value)?),365 )?;366 if !assertion_result {367 in_frame(368 CallLocation::new(&value.span()),369 || "assertion failure".to_owned(),370 || {371 if let Some(msg) = msg {372 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));373 }374 bail!(AssertionFailed(Val::Null.to_string()?));375 },376 )?;377 }378 Ok(())379}380381pub fn evaluate_named(ctx: Context, expr: &Spanned<Expr>, name: IStr) -> Result<Val> {382 use Expr::*;383 Ok(match &**expr {384 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),385 _ => evaluate(ctx, expr)?,386 })387}388389#[allow(clippy::too_many_lines)]390pub fn evaluate(ctx: Context, expr: &Spanned<Expr>) -> Result<Val> {391 use Expr::*;392393 if let Some(trivial) = evaluate_trivial(expr) {394 return Ok(trivial);395 }396 let loc = expr.span();397 Ok(match &**expr {398 Literal(LiteralType::This) => Val::Obj(ctx.try_this()?),399 Literal(LiteralType::Super) => Val::Obj(ctx.try_sup_this()?.standalone_super()?),400 Literal(LiteralType::Dollar) => Val::Obj(ctx.try_dollar()?),401 Literal(LiteralType::True) => Val::Bool(true),402 Literal(LiteralType::False) => Val::Bool(false),403 Literal(LiteralType::Null) => Val::Null,404 Str(v) => Val::string(v.clone()),405 Num(v) => Val::try_num(*v)?,406 // I have tried to remove special behavior from super by implementing standalone-super407 // expresion, but looks like this case still needs special treatment.408 //409 // Note that other jsonnet implementations will fail on `if value in (super)` expression,410 // because the standalone super literal is not supported, that is because in other411 // implementations `in super` treated differently from `in smth_else`.412 BinaryOp(bin)413 if matches!(&*bin.rhs, Expr::Literal(LiteralType::Super))414 && bin.op == BinaryOpType::In =>415 {416 let sup_this = ctx.try_sup_this()?;417 // In jsonnet, "field" in e is eager, LHS expression is always executed regardless of super existence.418 // In jrsonnet, however, this wasn't true, this was kept here for compatibility.419 if !sup_this.has_super() {420 return Ok(Val::Bool(false));421 }422 let field = evaluate(ctx, &bin.lhs)?;423 Val::Bool(sup_this.field_in_super(field.to_string()?))424 }425 BinaryOp(bin) => evaluate_binary_op_special(ctx, &bin.lhs, bin.op, &bin.rhs)?,426 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,427 Var(name) => in_frame(428 CallLocation::new(&loc),429 || format!("local <{name}> access"),430 || ctx.binding(name.clone())?.evaluate(),431 )?,432 Index { indexable, parts } => ensure_sufficient_stack(|| {433 let mut parts = parts.iter();434 let mut indexable = if matches!(&***indexable, Expr::Literal(LiteralType::Super)) {435 let part = parts.next().expect("at least part should exist");436 // sup_this existence check might also be skipped here for null-coalesce...437 // But I believe this might cause errors.438 let sup_this = ctx.try_sup_this()?;439 if !sup_this.has_super() {440 #[cfg(feature = "exp-null-coaelse")]441 if part.null_coaelse {442 return Ok(Val::Null);443 }444 bail!(NoSuperFound)445 }446 let name = evaluate(ctx.clone(), &part.value)?;447448 let Val::Str(name) = name else {449 bail!(ValueIndexMustBeTypeGot(450 ValType::Obj,451 ValType::Str,452 name.value_type(),453 ))454 };455456 let name = name.into_flat();457 match sup_this458 .get_super(name.clone())459 .with_description_src(&part.value, || format!("field <{name}> access"))?460 {461 Some(v) => v,462 #[cfg(feature = "exp-null-coaelse")]463 None if part.null_coaelse => return Ok(Val::Null),464 None => {465 let suggestions = suggest_object_fields(466 &sup_this.standalone_super().expect("super exists"),467 name.clone(),468 );469470 bail!(NoSuchField(name, suggestions))471 }472 }473 } else {474 evaluate(ctx.clone(), indexable)?475 };476477 for part in parts {478 indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {479 (Val::Obj(v), Val::Str(key)) => match v480 .get(key.clone().into_flat())481 .with_description_src(&part.value, || format!("field <{key}> access"))?482 {483 Some(v) => v,484 #[cfg(feature = "exp-null-coaelse")]485 None if part.null_coaelse => return Ok(Val::Null),486 None => {487 let suggestions = suggest_object_fields(&v, key.clone().into_flat());488489 return Err(Error::from(NoSuchField(490 key.clone().into_flat(),491 suggestions,492 )))493 .with_description_src(&part.value, || format!("field <{key}> access"));494 }495 },496 (Val::Obj(_), n) => bail!(ValueIndexMustBeTypeGot(497 ValType::Obj,498 ValType::Str,499 n.value_type(),500 )),501 (Val::Arr(v), Val::Num(n)) => {502 let n = n.get();503 if n.fract() > f64::EPSILON {504 bail!(FractionalIndex)505 }506 if n < 0.0 {507 bail!(ArrayBoundsError(n as isize, v.len()));508 }509 v.get(n as usize)?510 .ok_or_else(|| ArrayBoundsError(n as isize, v.len()))?511 }512 (Val::Arr(_), Val::Str(n)) => {513 bail!(AttemptedIndexAnArrayWithString(n.into_flat()))514 }515 (Val::Arr(_), n) => bail!(ValueIndexMustBeTypeGot(516 ValType::Arr,517 ValType::Num,518 n.value_type(),519 )),520521 (Val::Str(s), Val::Num(n)) => Val::Str({522 let n = n.get();523 if n.fract() > f64::EPSILON {524 bail!(FractionalIndex)525 }526 if n < 0.0 {527 bail!(ArrayBoundsError(n as isize, s.into_flat().chars().count()));528 }529 let v: IStr = s530 .clone()531 .into_flat()532 .chars()533 .skip(n as usize)534 .take(1)535 .collect::<String>()536 .into();537 if v.is_empty() {538 bail!(StringBoundsError(n as usize, s.into_flat().chars().count()))539 }540 StrValue::Flat(v)541 }),542 (Val::Str(_), n) => bail!(ValueIndexMustBeTypeGot(543 ValType::Str,544 ValType::Num,545 n.value_type(),546 )),547 #[cfg(feature = "exp-null-coaelse")]548 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),549 (v, _) => bail!(CantIndexInto(v.value_type())),550 };551 }552 Ok(indexable)553 })?,554 LocalExpr(bindings, returned) => {555 let mut new_bindings: FxHashMap<IStr, Thunk<Val>> =556 FxHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum());557 let fctx = Context::new_future();558 for b in bindings {559 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;560 }561 let ctx = ctx.extend_bindings(new_bindings).into_future(fctx);562 evaluate(ctx, &returned.clone())?563 }564 Arr(items) => {565 if items.is_empty() {566 Val::Arr(ArrValue::empty())567 } else {568 Val::Arr(ArrValue::expr(ctx, items.clone()))569 }570 }571 ArrComp(expr, comp_specs) => {572 let mut out = Vec::new();573 evaluate_comp(ctx, comp_specs, &mut |ctx| {574 let expr = expr.clone();575 out.push(Thunk!(move || evaluate(ctx, &expr)));576 Ok(())577 })?;578 Val::Arr(ArrValue::lazy(out))579 }580 Obj(body) => Val::Obj(evaluate_object(ctx, body)?),581 ObjExtend(a, b) => evaluate_add_op(582 &evaluate(ctx.clone(), a)?,583 &Val::Obj(evaluate_object(ctx, b)?),584 )?,585 Apply(value, args, tailstrict) => ensure_sufficient_stack(|| {586 evaluate_apply(ctx, value, args, CallLocation::new(&loc), *tailstrict)587 })?,588 Function(params, body) => {589 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())590 }591 AssertExpr(assert) => {592 evaluate_assert(ctx.clone(), &assert.assert)?;593 evaluate(ctx, &assert.rest)?594 }595 ErrorStmt(e) => in_frame(596 CallLocation::new(&loc),597 || "error statement".to_owned(),598 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),599 )?,600 IfElse (if_else)601 // {602 // cond,603 // cond_then,604 // cond_else,605 // }606 => {607 if in_frame(608 CallLocation::new(&loc),609 || "if condition".to_owned(),610 || bool::from_untyped(evaluate(ctx.clone(), &if_else.cond.0)?),611 )? {612 evaluate(ctx, &if_else.cond_then)?613 } else {614 match &if_else.cond_else {615 Some(v) => evaluate(ctx, v)?,616 None => Val::Null,617 }618 }619 }620 Slice(slice) => {621 fn parse_idx<T: Typed>(622 loc: CallLocation<'_>,623 ctx: Context,624 expr: Option<&Spanned<Expr>>,625 desc: &'static str,626 ) -> Result<Option<T>> {627 if let Some(value) = expr {628 Ok(in_frame(629 loc,630 || format!("slice {desc}"),631 || <Option<T>>::from_untyped(evaluate(ctx, value)?),632 )?)633 } else {634 Ok(None)635 }636 }637638 let indexable = evaluate(ctx.clone(), &slice.value)?;639 let loc = CallLocation::new(&loc);640641 let start = parse_idx(loc, ctx.clone(), slice.slice.start.as_ref(), "start")?;642 let end = parse_idx(loc, ctx.clone(), slice.slice.end.as_ref(), "end")?;643 let step = parse_idx(loc, ctx, slice.slice.step.as_ref(), "step")?;644645 IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?646 }647 Import(kind, path) => {648 let Expr::Str(path) = &***path else {649 bail!("computed imports are not supported")650 };651 let tmp = loc.clone().0;652 with_state(|s| {653 let resolved_path = s.resolve_from(tmp.source_path(), path)?;654 Ok(match kind {655 ImportKind::Normal => in_frame(656 CallLocation::new(&loc),657 || format!("import {:?}", path.clone()),658 || s.import_resolved(resolved_path),659 )?,660 ImportKind::Str => Val::string(s.import_resolved_str(resolved_path)?),661 ImportKind::Bin => {662 Val::Arr(ArrValue::bytes(s.import_resolved_bin(resolved_path)?))663 }664 }) as Result<Val>665 })?666 }667 })668}crates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -47,10 +47,10 @@
}
#[derive(Debug, PartialEq, Acyclic)]
-pub enum Member {
+pub(crate) enum Member {
Field(FieldMember),
BindStmt(BindSpec),
- AssertStmt(Rc<AssertStmt>),
+ AssertStmt(AssertStmt),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Acyclic)]
@@ -240,7 +240,7 @@
}
}
-#[derive(Debug, Clone, PartialEq, Acyclic)]
+#[derive(Debug, PartialEq, Acyclic)]
pub enum BindSpec {
Field {
into: Destruct,
@@ -275,15 +275,21 @@
#[derive(Debug, PartialEq, Acyclic)]
pub struct ObjComp {
- pub pre_locals: Vec<BindSpec>,
+ pub locals: Rc<Vec<BindSpec>>,
pub field: Rc<FieldMember>,
- pub post_locals: Vec<BindSpec>,
pub compspecs: Vec<CompSpec>,
}
#[derive(Debug, PartialEq, Acyclic)]
+pub struct ObjMembers {
+ pub locals: Rc<Vec<BindSpec>>,
+ pub asserts: Rc<Vec<AssertStmt>>,
+ pub fields: Vec<FieldMember>,
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
pub enum ObjBody {
- MemberList(Vec<Member>),
+ MemberList(ObjMembers),
ObjComp(ObjComp),
}
crates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -139,8 +139,8 @@
/ obj:destruct_object(s) {obj}
pub rule bind(s: &ParserSettings) -> expr::BindSpec
- = into:destruct(s) _ "=" _ expr:expr(s) {expr::BindSpec::Field{into, value: Rc::new(expr)}}
- / name:id() _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec::Function{name, params, value: Rc::new(expr)}}
+ = into:destruct(s) _ "=" _ value:expr(s) {expr::BindSpec::Field{into, value: Rc::new(value)}}
+ / name:id() _ "(" _ params:params(s) _ ")" _ "=" _ value:expr(s) {expr::BindSpec::Function{name, params, value: Rc::new(value)}}
pub rule assertion(s: &ParserSettings) -> expr::AssertStmt
= keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }
@@ -207,20 +207,35 @@
= keyword("local") _ bind:bind(s) {bind}
pub rule member(s: &ParserSettings) -> expr::Member
= bind:obj_local(s) {expr::Member::BindStmt(bind)}
- / assertion:assertion(s) {expr::Member::AssertStmt(Rc::new(assertion))}
+ / 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})* &"[" 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());
+ let mut locals = pre_locals;
+ locals.extend(post_locals);
expr::ObjBody::ObjComp(expr::ObjComp{
- pre_locals,
+ locals: Rc::new(locals),
field: Rc::new(field),
- post_locals,
compspecs,
})
}
- / members:(member(s) ** comma()) comma()? {expr::ObjBody::MemberList(members)}
+ / members:(member(s) ** comma()) comma()? {
+ let mut locals = Vec::new();
+ let mut asserts = Vec::new();
+ let mut fields = Vec::new();
+ for member in members {
+ match member {
+ Member::Field(field_member) => fields.push(field_member),
+ Member::BindStmt(bind_spec) => locals.push(bind_spec),
+ Member::AssertStmt(assert_stmt) => asserts.push(assert_stmt),
+ }
+ }
+ expr::ObjBody::MemberList(ObjMembers {
+ locals: Rc::new(locals), asserts: Rc::new(asserts), fields
+ })
+ }
pub rule ifspec(s: &ParserSettings) -> IfSpecData
= keyword("if") _ expr:expr(s) {IfSpecData(expr)}
pub rule forspec(s: &ParserSettings) -> ForSpecData
crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__add_location_info_to_all_sub_expressions.snapdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__add_location_info_to_all_sub_expressions.snap
+++ b/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__add_location_info_to_all_sub_expressions.snap
@@ -7,12 +7,16 @@
lhs: ObjExtend(
Obj(
MemberList(
- [],
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [],
+ },
),
) from virtual:<test>:0-2,
MemberList(
- [
- BindStmt(
+ ObjMembers {
+ locals: [
Field {
into: Full(
"x",
@@ -21,8 +25,9 @@
1.0,
) from virtual:<test>:15-16,
},
- ),
- Field(
+ ],
+ asserts: [],
+ fields: [
FieldMember {
name: Fixed(
"x",
@@ -34,14 +39,18 @@
"x",
) from virtual:<test>:21-22,
},
- ),
- ],
+ ],
+ },
),
) from virtual:<test>:0-24,
op: Add,
rhs: Obj(
MemberList(
- [],
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [],
+ },
),
) from virtual:<test>:27-29,
},
crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__empty_object.snapdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__empty_object.snap
+++ b/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__empty_object.snap
@@ -4,6 +4,10 @@
---
Obj(
MemberList(
- [],
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [],
+ },
),
) from virtual:<test>:0-2
crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__missing_newline_between_comment_and_eof.snapdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__missing_newline_between_comment_and_eof.snap
+++ b/crates/jrsonnet-parser/src/snapshots/jrsonnet_parser__tests__missing_newline_between_comment_and_eof.snap
@@ -4,8 +4,10 @@
---
Obj(
MemberList(
- [
- Field(
+ ObjMembers {
+ locals: [],
+ asserts: [],
+ fields: [
FieldMember {
name: Fixed(
"a",
@@ -17,7 +19,7 @@
1.0,
) from virtual:<test>:3-4,
},
- ),
- ],
+ ],
+ },
),
) from virtual:<test>:0-5