--- a/bindings/jsonnet/src/native.rs +++ b/bindings/jsonnet/src/native.rs @@ -9,7 +9,7 @@ use jrsonnet_evaluator::{ error::{Error, LocError}, function::builtin::{BuiltinParam, NativeCallback, NativeCallbackHandler}, - gc::TraceBox, + tb, typed::Typed, IStr, State, Val, }; @@ -78,9 +78,9 @@ vm.add_native( name, #[allow(deprecated)] - Cc::new(TraceBox(Box::new(NativeCallback::new( + Cc::new(tb!(NativeCallback::new( params, - TraceBox(Box::new(JsonnetNativeCallbackHandler { ctx, cb })), - )))), + tb!(JsonnetNativeCallbackHandler { ctx, cb }), + ))), ) } --- a/bindings/jsonnet/src/val_modify.rs +++ b/bindings/jsonnet/src/val_modify.rs @@ -5,7 +5,7 @@ use std::{ffi::CStr, os::raw::c_char}; use gcmodule::Cc; -use jrsonnet_evaluator::{val::ArrValue, LazyVal, State, Val}; +use jrsonnet_evaluator::{val::ArrValue, State, Thunk, Val}; /// # Safety /// @@ -18,7 +18,8 @@ for item in old.iter_lazy() { new.push(item); } - new.push(LazyVal::new_resolved(val.clone())); + + new.push(Thunk::evaluated(val.clone())); *arr = Val::Arr(ArrValue::Lazy(Cc::new(new))); } _ => panic!("should receive array"), --- a/cmds/jrsonnet/Cargo.toml +++ b/cmds/jrsonnet/Cargo.toml @@ -16,6 +16,8 @@ "jrsonnet-evaluator/exp-serde-preserve-order", "jrsonnet-cli/exp-preserve-order", ] +# Destructuring of locals +exp-destruct = ["jrsonnet-evaluator/exp-destruct"] [dependencies] jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.4.2" } --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -18,6 +18,8 @@ # Allows to preserve field order in objects exp-preserve-order = [] exp-serde-preserve-order = ["serde_json/preserve_order"] +# Implements field destructuring +exp-destruct = [] [dependencies] jrsonnet-interner = { path = "../jrsonnet-interner", version = "0.4.2" } --- a/crates/jrsonnet-evaluator/src/ctx.rs +++ b/crates/jrsonnet-evaluator/src/ctx.rs @@ -4,12 +4,12 @@ use jrsonnet_interner::IStr; use crate::{ - cc_ptr_eq, error::Error::*, gc::GcHashMap, map::LayeredHashMap, FutureWrapper, LazyBinding, - LazyVal, ObjValue, Result, State, Val, + cc_ptr_eq, error::Error::*, gc::GcHashMap, map::LayeredHashMap, LazyBinding, ObjValue, Pending, + Result, State, Thunk, Val, }; #[derive(Clone, Trace)] -pub struct ContextCreator(pub Context, pub FutureWrapper>); +pub struct ContextCreator(pub Context, pub Pending>); impl ContextCreator { pub fn create( &self, @@ -43,8 +43,8 @@ #[derive(Debug, Clone, Trace)] pub struct Context(Cc); impl Context { - pub fn new_future() -> FutureWrapper { - FutureWrapper::new() + pub fn new_future() -> Pending { + Pending::new() } pub fn dollar(&self) -> &Option { @@ -68,7 +68,7 @@ })) } - pub fn binding(&self, name: IStr) -> Result { + pub fn binding(&self, name: IStr) -> Result> { Ok(self .0 .bindings @@ -80,7 +80,7 @@ self.0.bindings.contains_key(&name) } #[must_use] - pub fn into_future(self, ctx: FutureWrapper) -> Self { + pub fn into_future(self, ctx: Pending) -> Self { { ctx.0.borrow_mut().replace(self); } @@ -90,7 +90,7 @@ #[must_use] pub fn with_var(self, name: IStr, value: Val) -> Self { let mut new_bindings = GcHashMap::with_capacity(1); - new_bindings.insert(name, LazyVal::new_resolved(value)); + new_bindings.insert(name, Thunk::evaluated(value)); self.extend(new_bindings, None, None, None) } @@ -102,7 +102,7 @@ #[must_use] pub fn extend( self, - new_bindings: GcHashMap, + new_bindings: GcHashMap>, new_dollar: Option, new_this: Option, new_super_obj: Option, @@ -124,7 +124,7 @@ })) } #[must_use] - pub fn extend_bound(self, new_bindings: GcHashMap) -> Self { + pub fn extend_bound(self, new_bindings: GcHashMap>) -> Self { let new_this = self.0.this.clone(); let new_super_obj = self.0.super_obj.clone(); self.extend(new_bindings, None, new_this, new_super_obj) --- a/crates/jrsonnet-evaluator/src/dynamic.rs +++ b/crates/jrsonnet-evaluator/src/dynamic.rs @@ -3,8 +3,8 @@ use gcmodule::{Cc, Trace}; #[derive(Clone, Trace)] -pub struct FutureWrapper(pub Cc>>); -impl FutureWrapper { +pub struct Pending(pub Cc>>); +impl Pending { pub fn new() -> Self { Self(Cc::new(RefCell::new(None))) } @@ -15,7 +15,7 @@ self.0.borrow_mut().replace(value); } } -impl FutureWrapper { +impl Pending { /// # Panics /// If wrapper is not yet filled pub fn unwrap(&self) -> T { @@ -23,7 +23,7 @@ } } -impl Default for FutureWrapper { +impl Default for Pending { fn default() -> Self { Self::new() } --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -45,6 +45,9 @@ #[error("variable is not defined: {0}")] VariableIsNotDefined(IStr), + #[error("duplicate local var: {0}")] + DuplicateLocalVar(IStr), + #[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{}", e)).collect::>().join(", "))] TypeMismatch(&'static str, Vec, ValType), #[error("no such field: {0}")] --- /dev/null +++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs @@ -0,0 +1,294 @@ +use gcmodule::Trace; +use jrsonnet_interner::IStr; +use jrsonnet_parser::{BindSpec, Destruct, LocExpr, ParamsDesc}; + +use crate::{ + error::{Error::*, Result}, + evaluate, evaluate_method, + gc::GcHashMap, + tb, throw, + val::ThunkValue, + Context, Pending, State, Thunk, Val, +}; + +fn destruct( + d: &Destruct, + parent: Thunk, + new_bindings: &mut GcHashMap>, +) -> Result<()> { + Ok(match d { + Destruct::Full(v) => { + let old = new_bindings.insert(v.clone(), parent); + if old.is_some() { + throw!(DuplicateLocalVar(v.clone())) + } + } + #[cfg(feature = "exp-destruct")] + Destruct::Skip => {} + #[cfg(feature = "exp-destruct")] + Destruct::Array { start, rest, end } => { + use jrsonnet_parser::DestructRest; + + use crate::{throw_runtime, val::ArrValue}; + + #[derive(Trace)] + struct DataThunk { + parent: Thunk, + min_len: usize, + has_rest: bool, + } + impl ThunkValue for DataThunk { + type Output = ArrValue; + + fn get(self: Box, s: State) -> Result { + let v = self.parent.evaluate(s)?; + let arr = match v { + Val::Arr(a) => a, + _ => throw_runtime!("expected array"), + }; + if !self.has_rest { + if arr.len() != self.min_len { + throw_runtime!("expected {} elements, got {}", self.min_len, arr.len()) + } + } else if arr.len() < self.min_len { + throw_runtime!( + "expected at least {} elements, but array was only {}", + self.min_len, + arr.len() + ) + } + Ok(arr) + } + } + + let full = Thunk::new(tb!(DataThunk { + min_len: start.len() + end.len(), + has_rest: rest.is_some(), + parent, + })); + + { + #[derive(Trace)] + struct BaseThunk { + full: Thunk, + index: usize, + } + impl ThunkValue for BaseThunk { + type Output = Val; + + fn get(self: Box, s: State) -> Result { + let full = self.full.evaluate(s.clone())?; + Ok(full.get(s, self.index)?.expect("length is checked")) + } + } + for (i, d) in start.iter().enumerate() { + destruct( + d, + Thunk::new(tb!(BaseThunk { + full: full.clone(), + index: i, + })), + new_bindings, + )?; + } + } + + match rest { + Some(DestructRest::Keep(v)) => { + #[derive(Trace)] + struct RestThunk { + full: Thunk, + start: usize, + end: usize, + } + impl ThunkValue for RestThunk { + type Output = Val; + + fn get(self: Box, s: State) -> Result { + let full = self.full.evaluate(s)?; + let to = full.len() - self.end; + Ok(Val::Arr(full.slice(Some(self.start), Some(to), None))) + } + } + + destruct( + &Destruct::Full(v.clone()), + Thunk::new(tb!(RestThunk { + full: full.clone(), + start: start.len(), + end: end.len(), + })), + new_bindings, + )?; + } + Some(DestructRest::Drop) => {} + None => {} + } + + { + #[derive(Trace)] + struct EndThunk { + full: Thunk, + index: usize, + end: usize, + } + impl ThunkValue for EndThunk { + type Output = Val; + + fn get(self: Box, s: State) -> Result { + let full = self.full.evaluate(s.clone())?; + Ok(full + .get(s, full.len() - self.end + self.index)? + .expect("length is checked")) + } + } + for (i, d) in end.iter().enumerate() { + destruct( + d, + Thunk::new(tb!(EndThunk { + full: full.clone(), + index: i, + end: end.len(), + })), + new_bindings, + )?; + } + } + } + #[cfg(feature = "exp-destruct")] + Destruct::Object { fields, rest } => { + use jrsonnet_parser::DestructRest; + + use crate::{obj::ObjValue, throw_runtime}; + + #[derive(Trace)] + struct DataThunk { + parent: Thunk, + field_names: Vec, + has_rest: bool, + } + impl ThunkValue for DataThunk { + type Output = ObjValue; + + fn get(self: Box, s: State) -> Result { + let v = self.parent.evaluate(s)?; + let obj = match v { + Val::Obj(o) => o, + _ => throw_runtime!("expected object"), + }; + for field in &self.field_names { + if !obj.has_field_ex(field.clone(), true) { + throw_runtime!("missing field: {}", field); + } + } + if !self.has_rest { + let len = obj.len(); + if len != self.field_names.len() { + throw_runtime!("too many fields, and rest not found"); + } + } + Ok(obj) + } + } + let field_names: Vec<_> = fields.iter().map(|f| f.0.clone()).collect(); + let full = Thunk::new(tb!(DataThunk { + parent, + field_names: field_names.clone(), + has_rest: rest.is_some() + })); + + for (field, d) in fields { + #[derive(Trace)] + struct FieldThunk { + full: Thunk, + field: IStr, + } + impl ThunkValue for FieldThunk { + type Output = Val; + + fn get(self: Box, s: State) -> Result { + let full = self.full.evaluate(s.clone())?; + let field = full.get(s, self.field)?.expect("shape is checked"); + Ok(field) + } + } + let value = Thunk::new(tb!(FieldThunk { + full: full.clone(), + field: field.clone() + })); + if let Some(d) = d { + destruct(d, value, new_bindings)?; + } else { + destruct(&Destruct::Full(field.clone()), value, new_bindings)?; + } + } + } + }) +} + +pub fn evaluate_dest( + d: &BindSpec, + fctx: Pending, + new_bindings: &mut GcHashMap>, +) -> Result<()> { + match d { + BindSpec::Field { into, value } => { + #[derive(Trace)] + struct EvaluateThunkValue { + fctx: Pending, + expr: LocExpr, + } + impl ThunkValue for EvaluateThunkValue { + type Output = Val; + fn get(self: Box, s: State) -> Result { + evaluate(s, self.fctx.unwrap(), &self.expr) + } + } + // TODO: Generate some name, as destructure spec may be used with plain functions + let data = Thunk::new(tb!(EvaluateThunkValue { + fctx, + expr: value.clone(), + })); + destruct(into, data, new_bindings)?; + } + BindSpec::Function { + name, + params, + value, + } => { + #[derive(Trace)] + struct MethodThunk { + fctx: Pending, + name: IStr, + params: ParamsDesc, + value: LocExpr, + } + impl ThunkValue for MethodThunk { + type Output = Val; + + fn get(self: Box, _s: State) -> Result { + Ok(evaluate_method( + self.fctx.unwrap(), + self.name, + self.params, + self.value, + )) + } + } + + let old = new_bindings.insert( + name.clone(), + Thunk::new(tb!(MethodThunk { + fctx, + name: name.clone(), + params: params.clone(), + value: value.clone() + })), + ); + if old.is_some() { + throw!(DuplicateLocalVar(name.clone())) + } + } + } + Ok(()) +} --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -1,189 +1,157 @@ use gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; use jrsonnet_parser::{ - ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, FieldMember, ForSpecData, IfSpecData, + ArgsDesc, AssertStmt, BindSpec, CompSpec, Destruct, Expr, FieldMember, ForSpecData, IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc, }; use jrsonnet_types::ValType; use crate::{ + destructure::evaluate_dest, error::Error::*, evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op}, function::{CallLocation, FuncDesc, FuncVal}, - gc::TraceBox, stdlib::{std_slice, BUILTINS}, - throw, + tb, throw, typed::Typed, - val::{ArrValue, LazyValValue}, - Bindable, Context, ContextCreator, FutureWrapper, GcHashMap, LazyBinding, LazyVal, ObjValue, - ObjValueBuilder, ObjectAssertion, Result, State, Val, + val::{ArrValue, Thunk, ThunkValue}, + Bindable, Context, ContextCreator, GcHashMap, LazyBinding, ObjValue, ObjValueBuilder, + ObjectAssertion, Pending, Result, State, Val, }; +pub mod destructure; pub mod operator; -pub fn evaluate_binding_in_future(b: &BindSpec, fctx: FutureWrapper) -> LazyVal { - let b = b.clone(); - if let Some(params) = &b.params { - #[derive(Trace)] - struct LazyMethodBinding { - fctx: FutureWrapper, - name: IStr, - params: ParamsDesc, - value: LocExpr, - } - impl LazyValValue for LazyMethodBinding { - fn get(self: Box, _: State) -> Result { - Ok(evaluate_method( - self.fctx.unwrap(), - self.name, - self.params, - self.value, - )) +#[allow(clippy::too_many_lines)] +pub fn evaluate_binding(b: BindSpec, cctx: ContextCreator) -> Result<(IStr, LazyBinding)> { + match b { + BindSpec::Field { + into: Destruct::Full(name), + value, + } => { + #[derive(Trace)] + struct BindableNamedThunk { + this: Option, + super_obj: Option, + + cctx: ContextCreator, + name: IStr, + value: LocExpr, } - } + impl ThunkValue for BindableNamedThunk { + type Output = Val; + fn get(self: Box, s: State) -> Result { + evaluate_named( + s.clone(), + self.cctx.create(s, self.this, self.super_obj)?, + &self.value, + self.name, + ) + } + } - let params = params.clone(); + #[derive(Trace)] + struct BindableNamed { + cctx: ContextCreator, + name: IStr, + value: LocExpr, + } + impl Bindable for BindableNamed { + fn bind( + &self, + _: State, + this: Option, + super_obj: Option, + ) -> Result> { + Ok(Thunk::new(tb!(BindableNamedThunk { + this, + super_obj, - LazyVal::new(TraceBox(Box::new(LazyMethodBinding { - fctx, - name: b.name.clone(), - params, - value: b.value.clone(), - }))) - } else { - #[derive(Trace)] - struct LazyNamedBinding { - fctx: FutureWrapper, - name: IStr, - value: LocExpr, - } - impl LazyValValue for LazyNamedBinding { - fn get(self: Box, s: State) -> Result { - evaluate_named(s, self.fctx.unwrap(), &self.value, self.name) + cctx: self.cctx.clone(), + name: self.name.clone(), + value: self.value.clone(), + }))) + } } - } - LazyVal::new(TraceBox(Box::new(LazyNamedBinding { - fctx, - name: b.name.clone(), - value: b.value, - }))) - } -} -#[allow(clippy::too_many_lines)] -pub fn evaluate_binding(b: &BindSpec, cctx: ContextCreator) -> (IStr, LazyBinding) { - let b = b.clone(); - if let Some(params) = &b.params { - #[derive(Trace)] - struct BindableMethodLazyVal { - this: Option, - super_obj: Option, - - cctx: ContextCreator, - name: IStr, - params: ParamsDesc, - value: LocExpr, - } - impl LazyValValue for BindableMethodLazyVal { - fn get(self: Box, s: State) -> Result { - Ok(evaluate_method( - self.cctx.create(s, self.this, self.super_obj)?, - self.name, - self.params, - self.value, - )) - } + Ok(( + name.clone(), + LazyBinding::Bindable(Cc::new(tb!(BindableNamed { + cctx, + name: name.clone(), + value: value.clone(), + }))), + )) } - - #[derive(Trace)] - struct BindableMethod { - cctx: ContextCreator, - name: IStr, - params: ParamsDesc, - value: LocExpr, + #[cfg(feature = "exp-destruct")] + BindSpec::Field { into: _, .. } => { + use crate::throw_runtime; + throw_runtime!("destructuring is not yet supported here") } - impl Bindable for BindableMethod { - fn bind( - &self, - _: State, + BindSpec::Function { + name, + params, + value, + } => { + #[derive(Trace)] + struct BindableMethodThunk { this: Option, super_obj: Option, - ) -> Result { - Ok(LazyVal::new(TraceBox(Box::new(BindableMethodLazyVal { - this, - super_obj, - cctx: self.cctx.clone(), - name: self.name.clone(), - params: self.params.clone(), - value: self.value.clone(), - })))) + cctx: ContextCreator, + name: IStr, + params: ParamsDesc, + value: LocExpr, } - } + impl ThunkValue for BindableMethodThunk { + type Output = Val; + fn get(self: Box, s: State) -> Result { + Ok(evaluate_method( + self.cctx.create(s, self.this, self.super_obj)?, + self.name, + self.params, + self.value, + )) + } + } - let params = params.clone(); - - ( - b.name.clone(), - LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableMethod { - cctx, - name: b.name.clone(), - params, - value: b.value.clone(), - })))), - ) - } else { - #[derive(Trace)] - struct BindableNamedLazyVal { - this: Option, - super_obj: Option, + #[derive(Trace)] + struct BindableMethod { + cctx: ContextCreator, + name: IStr, + params: ParamsDesc, + value: LocExpr, + } + impl Bindable for BindableMethod { + fn bind( + &self, + _: State, + this: Option, + super_obj: Option, + ) -> Result> { + Ok(Thunk::::new(tb!(BindableMethodThunk { + this, + super_obj, - cctx: ContextCreator, - name: IStr, - value: LocExpr, - } - impl LazyValValue for BindableNamedLazyVal { - fn get(self: Box, s: State) -> Result { - evaluate_named( - s.clone(), - self.cctx.create(s, self.this, self.super_obj)?, - &self.value, - self.name, - ) + cctx: self.cctx.clone(), + name: self.name.clone(), + params: self.params.clone(), + value: self.value.clone(), + }))) + } } - } - #[derive(Trace)] - struct BindableNamed { - cctx: ContextCreator, - name: IStr, - value: LocExpr, - } - impl Bindable for BindableNamed { - fn bind( - &self, - _: State, - this: Option, - super_obj: Option, - ) -> Result { - Ok(LazyVal::new(TraceBox(Box::new(BindableNamedLazyVal { - this, - super_obj, + let params = params.clone(); - cctx: self.cctx.clone(), - name: self.name.clone(), - value: self.value.clone(), - })))) - } + Ok(( + name.clone(), + LazyBinding::Bindable(Cc::new(tb!(BindableMethod { + cctx, + name: name.clone(), + params, + value, + }))), + )) } - - ( - b.name.clone(), - LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableNamed { - cctx, - name: b.name.clone(), - value: b.value.clone(), - })))), - ) } } @@ -252,19 +220,20 @@ #[allow(clippy::too_many_lines)] pub fn evaluate_member_list_object(s: State, ctx: Context, members: &[Member]) -> Result { - let new_bindings = FutureWrapper::new(); - let future_this = FutureWrapper::new(); + let new_bindings = Pending::new(); + let future_this = Pending::new(); let cctx = ContextCreator(ctx.clone(), new_bindings.clone()); { let mut bindings: GcHashMap = GcHashMap::with_capacity(members.len()); - for (n, b) in members + for r in members .iter() .filter_map(|m| match m { Member::BindStmt(b) => Some(b.clone()), _ => None, }) - .map(|b| evaluate_binding(&b, cctx.clone())) + .map(|b| evaluate_binding(b.clone(), cctx.clone())) { + let (n, b) = r?; bindings.insert(n, b); } new_bindings.fill(bindings); @@ -292,8 +261,8 @@ s: State, this: Option, super_obj: Option, - ) -> Result { - Ok(LazyVal::new_resolved(evaluate_named( + ) -> Result> { + Ok(Thunk::evaluated(evaluate_named( s.clone(), self.cctx.create(s, this, super_obj)?, &self.value, @@ -316,11 +285,11 @@ .with_location(value.1.clone()) .bindable( s.clone(), - TraceBox(Box::new(ObjMemberBinding { + tb!(ObjMemberBinding { cctx: cctx.clone(), value: value.clone(), name, - })), + }), )?; } Member::Field(FieldMember { @@ -342,8 +311,8 @@ s: State, this: Option, super_obj: Option, - ) -> Result { - Ok(LazyVal::new_resolved(evaluate_method( + ) -> Result> { + Ok(Thunk::evaluated(evaluate_method( self.cctx.create(s, this, super_obj)?, self.name.clone(), self.params.clone(), @@ -364,12 +333,12 @@ .with_location(value.1.clone()) .bindable( s.clone(), - TraceBox(Box::new(ObjMemberBinding { + tb!(ObjMemberBinding { cctx: cctx.clone(), value: value.clone(), params: params.clone(), name, - })), + }), )?; } Member::BindStmt(_) => {} @@ -390,10 +359,10 @@ evaluate_assert(s, ctx, &self.assert) } } - builder.assert(TraceBox(Box::new(ObjectAssert { + builder.assert(tb!(ObjectAssert { cctx: cctx.clone(), assert: stmt.clone(), - }))); + })); } } } @@ -406,19 +375,20 @@ Ok(match object { ObjBody::MemberList(members) => evaluate_member_list_object(s, ctx, members)?, ObjBody::ObjComp(obj) => { - let future_this = FutureWrapper::new(); + let future_this = Pending::new(); let mut builder = ObjValueBuilder::new(); evaluate_comp(s.clone(), ctx, &obj.compspecs, &mut |ctx| { - let new_bindings = FutureWrapper::new(); + let new_bindings = Pending::new(); let cctx = ContextCreator(ctx.clone(), new_bindings.clone()); let mut bindings: GcHashMap = GcHashMap::with_capacity(obj.pre_locals.len() + obj.post_locals.len()); - for (n, b) in obj + for r in obj .pre_locals .iter() .chain(obj.post_locals.iter()) - .map(|b| evaluate_binding(b, cctx.clone())) + .map(|b| evaluate_binding(b.clone(), cctx.clone())) { + let (n, b) = r?; bindings.insert(n, b); } new_bindings.fill(bindings.clone()); @@ -439,8 +409,8 @@ s: State, this: Option, _super_obj: Option, - ) -> Result { - Ok(LazyVal::new_resolved(evaluate( + ) -> Result> { + Ok(Thunk::evaluated(evaluate( s, self.ctx.clone().extend(GcHashMap::new(), None, this, None), &self.value, @@ -453,10 +423,10 @@ .with_add(obj.plus) .bindable( s.clone(), - TraceBox(Box::new(ObjCompBinding { + tb!(ObjCompBinding { ctx, value: obj.value.clone(), - })), + }), )?; } v => throw!(FieldMustBeStringGot(v.value_type())), @@ -620,11 +590,11 @@ } } LocalExpr(bindings, returned) => { - let mut new_bindings: GcHashMap = + let mut new_bindings: GcHashMap> = GcHashMap::with_capacity(bindings.len()); let fctx = Context::new_future(); for b in bindings { - new_bindings.insert(b.name.clone(), evaluate_binding_in_future(b, fctx.clone())); + evaluate_dest(b, fctx.clone(), &mut new_bindings)?; } let ctx = ctx.extend_bound(new_bindings).into_future(fctx); evaluate(s, ctx, &returned.clone())? @@ -638,15 +608,16 @@ ctx: Context, item: LocExpr, } - impl LazyValValue for ArrayElement { + impl ThunkValue for ArrayElement { + type Output = Val; fn get(self: Box, s: State) -> Result { evaluate(s, self.ctx, &self.item) } } - out.push(LazyVal::new(TraceBox(Box::new(ArrayElement { + out.push(Thunk::new(tb!(ArrayElement { ctx: ctx.clone(), item: item.clone(), - })))); + }))); } Val::Arr(out.into()) } --- a/crates/jrsonnet-evaluator/src/function/arglike.rs +++ b/crates/jrsonnet-evaluator/src/function/arglike.rs @@ -5,34 +5,34 @@ use jrsonnet_parser::{ArgsDesc, LocExpr}; use crate::{ - error::Result, evaluate, gc::TraceBox, typed::Typed, val::LazyValValue, Context, LazyVal, - State, Val, + error::Result, evaluate, tb, typed::Typed, val::ThunkValue, Context, State, Thunk, Val, }; #[derive(Trace)] -struct EvaluateLazyVal { +struct EvaluateThunk { ctx: Context, expr: LocExpr, } -impl LazyValValue for EvaluateLazyVal { +impl ThunkValue for EvaluateThunk { + type Output = Val; fn get(self: Box, s: State) -> Result { evaluate(s, self.ctx, &self.expr) } } pub trait ArgLike { - fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result; + fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result>; } impl ArgLike for &LocExpr { - fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result { + fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result> { Ok(if tailstrict { - LazyVal::new_resolved(evaluate(s, ctx, self)?) + Thunk::evaluated(evaluate(s, ctx, self)?) } else { - LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { + Thunk::new(tb!(EvaluateThunk { ctx, expr: (*self).clone(), - }))) + })) }) } } @@ -41,9 +41,9 @@ where T: Typed + Clone, { - fn evaluate_arg(&self, s: State, _ctx: Context, _tailstrict: bool) -> Result { + fn evaluate_arg(&self, s: State, _ctx: Context, _tailstrict: bool) -> Result> { let val = T::into_untyped(self.clone(), s)?; - Ok(LazyVal::new_resolved(val)) + Ok(Thunk::evaluated(val)) } } @@ -53,18 +53,18 @@ Val(Val), } impl ArgLike for TlaArg { - fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result { + fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result> { match self { - TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))), + TlaArg::String(s) => Ok(Thunk::evaluated(Val::Str(s.clone()))), TlaArg::Code(code) => Ok(if tailstrict { - LazyVal::new_resolved(evaluate(s, ctx, code)?) + Thunk::evaluated(evaluate(s, ctx, code)?) } else { - LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { + Thunk::new(tb!(EvaluateThunk { ctx, expr: code.clone(), - }))) + })) }), - TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())), + TlaArg::Val(val) => Ok(Thunk::evaluated(val.clone())), } } } @@ -83,14 +83,14 @@ s: State, ctx: Context, tailstrict: bool, - handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>, + handler: &mut dyn FnMut(usize, Thunk) -> Result<()>, ) -> Result<()>; fn named_iter( &self, s: State, ctx: Context, tailstrict: bool, - handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>, + handler: &mut dyn FnMut(&IStr, Thunk) -> Result<()>, ) -> Result<()>; fn named_names(&self, handler: &mut dyn FnMut(&IStr)); } @@ -105,18 +105,18 @@ s: State, ctx: Context, tailstrict: bool, - handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>, + handler: &mut dyn FnMut(usize, Thunk) -> Result<()>, ) -> Result<()> { for (id, arg) in self.unnamed.iter().enumerate() { handler( id, if tailstrict { - LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?) + Thunk::evaluated(evaluate(s.clone(), ctx.clone(), arg)?) } else { - LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { + Thunk::new(tb!(EvaluateThunk { ctx: ctx.clone(), expr: arg.clone(), - }))) + })) }, )?; } @@ -128,18 +128,18 @@ s: State, ctx: Context, tailstrict: bool, - handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>, + handler: &mut dyn FnMut(&IStr, Thunk) -> Result<()>, ) -> Result<()> { for (name, arg) in &self.named { handler( name, if tailstrict { - LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?) + Thunk::evaluated(evaluate(s.clone(), ctx.clone(), arg)?) } else { - LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { + Thunk::new(tb!(EvaluateThunk { ctx: ctx.clone(), expr: arg.clone(), - }))) + })) }, )?; } @@ -164,7 +164,7 @@ _s: State, _ctx: Context, _tailstrict: bool, - _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>, + _handler: &mut dyn FnMut(usize, Thunk) -> Result<()>, ) -> Result<()> { Ok(()) } @@ -174,7 +174,7 @@ s: State, ctx: Context, tailstrict: bool, - handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>, + handler: &mut dyn FnMut(&IStr, Thunk) -> Result<()>, ) -> Result<()> { for (name, value) in self.iter() { handler( @@ -205,7 +205,7 @@ s: State, ctx: Context, tailstrict: bool, - handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>, + handler: &mut dyn FnMut(usize, Thunk) -> Result<()>, ) -> Result<()> { let mut i = 0usize; let ($($gen,)*) = self; @@ -220,7 +220,7 @@ _s: State, _ctx: Context, _tailstrict: bool, - _handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>, + _handler: &mut dyn FnMut(&IStr, Thunk) -> Result<()>, ) -> Result<()> { Ok(()) } @@ -236,7 +236,7 @@ _s: State, _ctx: Context, _tailstrict: bool, - _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>, + _handler: &mut dyn FnMut(usize, Thunk) -> Result<()>, ) -> Result<()> { Ok(()) } @@ -246,7 +246,7 @@ s: State, ctx: Context, tailstrict: bool, - handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>, + handler: &mut dyn FnMut(&IStr, Thunk) -> Result<()>, ) -> Result<()> { let ($($gen,)*) = self; $( @@ -285,7 +285,7 @@ _s: State, _ctx: Context, _tailstrict: bool, - _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>, + _handler: &mut dyn FnMut(usize, Thunk) -> Result<()>, ) -> Result<()> { Ok(()) } @@ -295,7 +295,7 @@ _s: State, _ctx: Context, _tailstrict: bool, - _handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>, + _handler: &mut dyn FnMut(&IStr, Thunk) -> Result<()>, ) -> Result<()> { Ok(()) } --- a/crates/jrsonnet-evaluator/src/function/parse.rs +++ b/crates/jrsonnet-evaluator/src/function/parse.rs @@ -9,20 +9,21 @@ use crate::{ error::{Error::*, Result}, evaluate_named, - gc::{GcHashMap, TraceBox}, - throw, - val::LazyValValue, - Context, FutureWrapper, LazyVal, State, Val, + gc::GcHashMap, + tb, throw, + val::ThunkValue, + Context, Pending, State, Thunk, Val, }; #[derive(Trace)] -struct EvaluateNamedLazyVal { - ctx: FutureWrapper, +struct EvaluateNamedThunk { + ctx: Pending, name: IStr, value: LocExpr, } -impl LazyValValue for EvaluateNamedLazyVal { +impl ThunkValue for EvaluateNamedThunk { + type Output = Val; fn get(self: Box, s: State) -> Result { evaluate_named(s, self.ctx.unwrap(), &self.value, self.name) } @@ -83,11 +84,11 @@ defaults.insert( param.0.clone(), - LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal { + Thunk::new(tb!(EvaluateNamedThunk { ctx: fctx.clone(), name: param.0.clone(), value: param.1.clone().expect("default exists"), - }))), + })), ); filled_args += 1; } @@ -131,7 +132,7 @@ params: &[BuiltinParam], args: &dyn ArgsLike, tailstrict: bool, -) -> Result> { +) -> Result>> { let mut passed_args = GcHashMap::with_capacity(params.len()); if args.unnamed_len() > params.len() { throw!(TooManyArgsFunctionHas(params.len())) @@ -191,7 +192,8 @@ pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context { #[derive(Trace)] struct DependsOnUnbound(IStr); - impl LazyValValue for DependsOnUnbound { + impl ThunkValue for DependsOnUnbound { + type Output = Val; fn get(self: Box, _: State) -> Result { Err(FunctionParameterNotBoundInCall(self.0.clone()).into()) } @@ -205,16 +207,16 @@ if let Some(v) = ¶m.1 { bindings.insert( param.0.clone(), - LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal { + Thunk::new(tb!(EvaluateNamedThunk { ctx: fctx.clone(), name: param.0.clone(), value: v.clone(), - }))), + })), ); } else { bindings.insert( param.0.clone(), - LazyVal::new(TraceBox(Box::new(DependsOnUnbound(param.0.clone())))), + Thunk::new(tb!(DependsOnUnbound(param.0.clone()))), ); } } --- a/crates/jrsonnet-evaluator/src/gc.rs +++ b/crates/jrsonnet-evaluator/src/gc.rs @@ -10,8 +10,15 @@ use rustc_hash::{FxHashMap, FxHashSet}; /// Replacement for box, which assumes that the underlying type is [`Trace`] +/// Used in places, where Cc should be used instead, but it can't, because CoerceUnsiced is not stable #[derive(Debug, Clone)] pub struct TraceBox(pub Box); +#[macro_export] +macro_rules! tb { + ($v:expr) => { + $crate::gc::TraceBox(Box::new($v)) + }; +} impl Trace for TraceBox { fn trace(&self, tracer: &mut Tracer) { --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -58,7 +58,7 @@ use jrsonnet_parser::*; pub use obj::*; use trace::{location_to_offset, offset_to_location, CodeLocation, CompactFormat, TraceFormat}; -pub use val::{LazyVal, ManifestFormat, Val}; +pub use val::{ManifestFormat, Thunk, Val}; pub trait Bindable: Trace + 'static { fn bind( @@ -66,13 +66,13 @@ s: State, this: Option, super_obj: Option, - ) -> Result; + ) -> Result>; } #[derive(Clone, Trace)] pub enum LazyBinding { Bindable(Cc>), - Bound(LazyVal), + Bound(Thunk), } impl Debug for LazyBinding { @@ -86,7 +86,7 @@ s: State, this: Option, super_obj: Option, - ) -> Result { + ) -> Result> { match self { Self::Bindable(v) => v.bind(s, this, super_obj), Self::Bound(v) => Ok(v.clone()), @@ -343,7 +343,7 @@ let globals = &self.settings().globals; let mut new_bindings = GcHashMap::with_capacity(globals.len()); for (name, value) in globals.iter() { - new_bindings.insert(name.clone(), LazyVal::new_resolved(value.clone())); + new_bindings.insert(name.clone(), Thunk::evaluated(value.clone())); } Context::new().extend_bound(new_bindings) } --- a/crates/jrsonnet-evaluator/src/map.rs +++ b/crates/jrsonnet-evaluator/src/map.rs @@ -1,27 +1,27 @@ use gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; -use crate::{GcHashMap, LazyVal}; +use crate::{GcHashMap, Thunk, Val}; #[derive(Trace)] #[force_tracking] pub struct LayeredHashMapInternals { parent: Option, - current: GcHashMap, + current: GcHashMap>, } #[derive(Trace)] pub struct LayeredHashMap(Cc); impl LayeredHashMap { - pub fn extend(self, new_layer: GcHashMap) -> Self { + pub fn extend(self, new_layer: GcHashMap>) -> Self { Self(Cc::new(LayeredHashMapInternals { parent: Some(self), current: new_layer, })) } - pub fn get(&self, key: &IStr) -> Option<&LazyVal> { + pub fn get(&self, key: &IStr) -> Option<&Thunk> { (self.0) .current .get(key) --- a/crates/jrsonnet-evaluator/src/obj.rs +++ b/crates/jrsonnet-evaluator/src/obj.rs @@ -16,7 +16,7 @@ function::CallLocation, gc::{GcHashMap, GcHashSet, TraceBox}, operator::evaluate_add_op, - throw, weak_ptr_eq, weak_raw, Bindable, LazyBinding, LazyVal, Result, State, Val, + throw, weak_ptr_eq, weak_raw, Bindable, LazyBinding, Result, State, Thunk, Val, }; #[cfg(not(feature = "exp-preserve-order"))] @@ -581,7 +581,7 @@ pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder); impl<'v> ObjMemberBuilder> { pub fn value(self, s: State, value: Val) -> Result<()> { - self.binding(s, LazyBinding::Bound(LazyVal::new_resolved(value))) + self.binding(s, LazyBinding::Bound(Thunk::evaluated(value))) } pub fn bindable(self, s: State, bindable: TraceBox) -> Result<()> { self.binding(s, LazyBinding::Bindable(Cc::new(bindable))) @@ -604,7 +604,7 @@ pub struct ExtendBuilder<'v>(&'v mut ObjValue); impl<'v> ObjMemberBuilder> { pub fn value(self, value: Val) { - self.binding(LazyBinding::Bound(LazyVal::new_resolved(value))); + self.binding(LazyBinding::Bound(Thunk::evaluated(value))); } pub fn bindable(self, bindable: TraceBox) { self.binding(LazyBinding::Bindable(Cc::new(bindable))); --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -15,41 +15,45 @@ throw, ObjValue, Result, State, }; -pub trait LazyValValue: Trace { - fn get(self: Box, s: State) -> Result; +pub trait ThunkValue: Trace { + type Output; + fn get(self: Box, s: State) -> Result; } #[derive(Trace)] -enum LazyValInternals { - Computed(Val), +enum ThunkInner { + Computed(T), Errored(LocError), - Waiting(TraceBox), + Waiting(TraceBox>), Pending, } #[allow(clippy::module_name_repetitions)] #[derive(Clone, Trace)] -pub struct LazyVal(Cc>); -impl LazyVal { - pub fn new(f: TraceBox) -> Self { - Self(Cc::new(RefCell::new(LazyValInternals::Waiting(f)))) +pub struct Thunk(Cc>>); +impl Thunk +where + T: Clone + Trace, +{ + pub fn new(f: TraceBox>) -> Self { + Self(Cc::new(RefCell::new(ThunkInner::Waiting(f)))) } - pub fn new_resolved(val: Val) -> Self { - Self(Cc::new(RefCell::new(LazyValInternals::Computed(val)))) + pub fn evaluated(val: T) -> Self { + Self(Cc::new(RefCell::new(ThunkInner::Computed(val)))) } pub fn force(&self, s: State) -> Result<()> { self.evaluate(s)?; Ok(()) } - pub fn evaluate(&self, s: State) -> Result { + pub fn evaluate(&self, s: State) -> Result { match &*self.0.borrow() { - LazyValInternals::Computed(v) => return Ok(v.clone()), - LazyValInternals::Errored(e) => return Err(e.clone()), - LazyValInternals::Pending => return Err(InfiniteRecursionDetected.into()), - LazyValInternals::Waiting(..) => (), + ThunkInner::Computed(v) => return Ok(v.clone()), + ThunkInner::Errored(e) => return Err(e.clone()), + ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()), + ThunkInner::Waiting(..) => (), }; - let value = if let LazyValInternals::Waiting(value) = - std::mem::replace(&mut *self.0.borrow_mut(), LazyValInternals::Pending) + let value = if let ThunkInner::Waiting(value) = + std::mem::replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) { value } else { @@ -58,21 +62,21 @@ let new_value = match value.0.get(s) { Ok(v) => v, Err(e) => { - *self.0.borrow_mut() = LazyValInternals::Errored(e.clone()); + *self.0.borrow_mut() = ThunkInner::Errored(e.clone()); return Err(e); } }; - *self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone()); + *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone()); Ok(new_value) } } -impl Debug for LazyVal { +impl Debug for Thunk { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Lazy") } } -impl PartialEq for LazyVal { +impl PartialEq for Thunk { fn eq(&self, other: &Self) -> bool { cc_ptr_eq(&self.0, &other.0) } @@ -142,7 +146,7 @@ #[force_tracking] pub enum ArrValue { Bytes(#[skip_trace] Rc<[u8]>), - Lazy(Cc>), + Lazy(Cc>>), Eager(Cc>), Extended(Box<(Self, Self)>), Range(i32, i32), @@ -240,13 +244,13 @@ } } - pub fn get_lazy(&self, index: usize) -> Option { + pub fn get_lazy(&self, index: usize) -> Option> { match self { Self::Bytes(i) => i .get(index) - .map(|b| LazyVal::new_resolved(Val::Num(f64::from(*b)))), + .map(|b| Thunk::evaluated(Val::Num(f64::from(*b)))), Self::Lazy(vec) => vec.get(index).cloned(), - Self::Eager(vec) => vec.get(index).cloned().map(LazyVal::new_resolved), + Self::Eager(vec) => vec.get(index).cloned().map(Thunk::evaluated), Self::Extended(v) => { let a_len = v.0.len(); if a_len > index { @@ -259,7 +263,7 @@ if index >= self.len() { return None; } - Some(LazyVal::new_resolved(Val::Num( + Some(Thunk::evaluated(Val::Num( ((*a as isize) + index as isize) as f64, ))) } @@ -343,11 +347,11 @@ }) } - pub fn iter_lazy(&self) -> impl DoubleEndedIterator + '_ { + pub fn iter_lazy(&self) -> impl DoubleEndedIterator> + '_ { (0..self.len()).map(move |idx| match self { - Self::Bytes(b) => LazyVal::new_resolved(Val::Num(f64::from(b[idx]))), + Self::Bytes(b) => Thunk::evaluated(Val::Num(f64::from(b[idx]))), Self::Lazy(l) => l[idx].clone(), - Self::Eager(e) => LazyVal::new_resolved(e[idx].clone()), + Self::Eager(e) => Thunk::evaluated(e[idx].clone()), Self::Slice(..) | Self::Extended(..) | Self::Range(..) | Self::Reversed(..) => { self.get_lazy(idx).expect("idx < len") } @@ -391,8 +395,8 @@ } } -impl From> for ArrValue { - fn from(v: Vec) -> Self { +impl From>> for ArrValue { + fn from(v: Vec>) -> Self { Self::Lazy(Cc::new(v)) } } --- a/crates/jrsonnet-evaluator/tests/builtin.rs +++ b/crates/jrsonnet-evaluator/tests/builtin.rs @@ -7,6 +7,7 @@ error::Result, function::{builtin, builtin::Builtin, CallLocation, FuncVal}, gc::TraceBox, + tb, typed::Typed, State, Val, }; @@ -70,9 +71,7 @@ #[builtin] fn curry_add(a: u32) -> Result { - Ok(FuncVal::Builtin(Cc::new(TraceBox(Box::new(curried_add { - a, - }))))) + Ok(FuncVal::Builtin(Cc::new(tb!(curried_add { a })))) } #[test] --- a/crates/jrsonnet-evaluator/tests/common.rs +++ b/crates/jrsonnet-evaluator/tests/common.rs @@ -1,7 +1,7 @@ use jrsonnet_evaluator::{ error::Result, function::{builtin, FuncVal}, - throw_runtime, LazyVal, ObjValueBuilder, State, Val, + throw_runtime, ObjValueBuilder, State, Thunk, Val, }; #[macro_export] @@ -38,7 +38,7 @@ } #[builtin] -fn assert_throw(s: State, lazy: LazyVal, message: String) -> Result { +fn assert_throw(s: State, lazy: Thunk, message: String) -> Result { match lazy.evaluate(s) { Ok(_) => { throw_runtime!("expected argument to throw on evaluation, but it returned instead") --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -150,7 +150,7 @@ return Ok(Self::State); } else if type_is_path(ty, "CallLocation").is_some() { return Ok(Self::Location); - } else if type_is_path(ty, "LazyVal").is_some() { + } else if type_is_path(ty, "Thunk").is_some() { return Ok(Self::Lazy { is_option: false, name: ident.to_string(), @@ -163,7 +163,7 @@ } let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? { - if type_is_path(ty, "LazyVal").is_some() { + if type_is_path(ty, "Thunk").is_some() { return Ok(Self::Lazy { is_option: true, name: ident.to_string(), --- a/crates/jrsonnet-parser/Cargo.toml +++ b/crates/jrsonnet-parser/Cargo.toml @@ -6,6 +6,9 @@ license = "MIT" edition = "2021" +[features] +exp-destruct = [] + [dependencies] jrsonnet-interner = { path = "../jrsonnet-interner", version = "0.4.2" } --- a/crates/jrsonnet-parser/src/expr.rs +++ b/crates/jrsonnet-parser/src/expr.rs @@ -179,10 +179,44 @@ #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Trace)] -pub struct BindSpec { - pub name: IStr, - pub params: Option, - pub value: LocExpr, +pub enum DestructRest { + /// ...rest + Keep(IStr), + /// ... + Drop, +} + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Trace)] +pub enum Destruct { + Full(IStr), + #[cfg(feature = "exp-destruct")] + Skip, + #[cfg(feature = "exp-destruct")] + Array { + start: Vec, + rest: Option, + end: Vec, + }, + #[cfg(feature = "exp-destruct")] + Object { + fields: Vec<(IStr, Option)>, + rest: Option, + }, +} + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, PartialEq, Trace)] +pub enum BindSpec { + Field { + into: Destruct, + value: LocExpr, + }, + Function { + name: IStr, + params: ParamsDesc, + value: LocExpr, + }, } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] --- a/crates/jrsonnet-parser/src/lib.rs +++ b/crates/jrsonnet-parser/src/lib.rs @@ -55,18 +55,18 @@ /// Reserved word followed by any non-alphanumberic rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "importbin" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident() - rule id() = quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("") + rule id() -> IStr = v:$(quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("")) { v.into() } rule keyword(id: &'static str) -> () = ##parse_string_literal(id) end_of_ident() - pub rule param(s: &ParserSettings) -> expr::Param = name:$(id()) expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name.into(), expr) } + pub rule param(s: &ParserSettings) -> expr::Param = name:id() expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name, expr) } pub rule params(s: &ParserSettings) -> expr::ParamsDesc = params:param(s) ** comma() comma()? { expr::ParamsDesc(Rc::new(params)) } / { expr::ParamsDesc(Rc::new(Vec::new())) } pub rule arg(s: &ParserSettings) -> (Option, LocExpr) - = quiet! { name:(s:$(id()) _ "=" _ {s})? expr:expr(s) {(name.map(Into::into), expr)} } + = quiet! { name:(s:id() _ "=" _ {s})? expr:expr(s) {(name, expr)} } / expected!("") pub rule args(s: &ParserSettings) -> expr::ArgsDesc @@ -89,9 +89,52 @@ Ok(expr::ArgsDesc::new(unnamed, named)) } + pub rule destruct_rest() -> expr::DestructRest + = "..." into:(_ into:id() {into})? {if let Some(into) = into { + expr::DestructRest::Keep(into) + } else {expr::DestructRest::Drop}} + pub rule destruct_array(s: &ParserSettings) -> expr::Destruct + = "[" _ start:destruct(s)**comma() rest:( + comma() _ rest:destruct_rest()? end:( + comma() end:destruct(s)**comma() (_ comma())? {end} + / comma()? {Vec::new()} + ) {(rest, end)} + / comma()? {(None, Vec::new())} + ) _ "]" {? + #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Array { + start, + rest: rest.0, + end: rest.1, + }); + #[cfg(not(feature = "exp-destruct"))] Err("experimental destructuring was not enabled") + } + pub rule destruct_object(s: &ParserSettings) -> expr::Destruct + = "{" _ + fields:(name:id() _ into:(":" _ into:destruct(s) {into})? {(name, into)})**comma() + rest:( + comma() rest:destruct_rest()? {rest} + / comma()? {None} + ) + _ "}" {? + #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Object { + fields, + rest, + }); + #[cfg(not(feature = "exp-destruct"))] Err("experimental destructuring was not enabled") + } + pub rule destruct(s: &ParserSettings) -> expr::Destruct + = v:id() {expr::Destruct::Full(v)} + / "?" {? + #[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Skip); + #[cfg(not(feature = "exp-destruct"))] Err("experimental destructuring was not enabled") + } + / arr:destruct_array(s) {arr} + / obj:destruct_object(s) {obj} + pub rule bind(s: &ParserSettings) -> expr::BindSpec - = name:$(id()) _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: None, value: expr}} - / name:$(id()) _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: Some(params), value: expr}} + = into:destruct(s) _ "=" _ expr:expr(s) {expr::BindSpec::Field{into, value: expr}} + / name:id() _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec::Function{name, params, value: expr}} + pub rule assertion(s: &ParserSettings) -> expr::AssertStmt = keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) } @@ -122,7 +165,7 @@ / string_block() } / expected!("") pub rule field_name(s: &ParserSettings) -> expr::FieldName - = name:$(id()) {expr::FieldName::Fixed(name.into())} + = name:id() {expr::FieldName::Fixed(name.into())} / name:string() {expr::FieldName::Fixed(name.into())} / "[" _ expr:expr(s) _ "]" {expr::FieldName::Dyn(expr)} pub rule visibility() -> expr::Visibility @@ -167,7 +210,7 @@ pub rule ifspec(s: &ParserSettings) -> IfSpecData = keyword("if") _ expr:expr(s) {IfSpecData(expr)} pub rule forspec(s: &ParserSettings) -> ForSpecData - = keyword("for") _ id:$(id()) _ keyword("in") _ cond:expr(s) {ForSpecData(id.into(), cond)} + = keyword("for") _ id:id() _ keyword("in") _ cond:expr(s) {ForSpecData(id, cond)} pub rule compspec(s: &ParserSettings) -> Vec = s:(i:ifspec(s) { expr::CompSpec::IfSpec(i) } / f:forspec(s) {expr::CompSpec::ForSpec(f)} ) ** _ {s} pub rule local_expr(s: &ParserSettings) -> Expr @@ -187,9 +230,9 @@ pub rule number_expr(s: &ParserSettings) -> Expr = n:number() { expr::Expr::Num(n) } pub rule var_expr(s: &ParserSettings) -> Expr - = n:$(id()) { expr::Expr::Var(n.into()) } + = n:id() { expr::Expr::Var(n) } pub rule id_loc(s: &ParserSettings) -> LocExpr - = a:position!() n:$(id()) b:position!() { LocExpr(Rc::new(expr::Expr::Str(n.into())), ExprLocation(s.file_name.clone(), a,b)) } + = a:position!() n:id() b:position!() { LocExpr(Rc::new(expr::Expr::Str(n)), ExprLocation(s.file_name.clone(), a,b)) } pub rule if_then_else_expr(s: &ParserSettings) -> Expr = cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{ cond, @@ -212,7 +255,7 @@ / quiet!{"$intrinsicThisFile" {Expr::IntrinsicThisFile}} / quiet!{"$intrinsicId" {Expr::IntrinsicId}} - / quiet!{"$intrinsic(" name:$(id()) ")" {Expr::Intrinsic(name.into())}} + / quiet!{"$intrinsic(" name:id() ")" {Expr::Intrinsic(name)}} / string_expr(s) / number_expr(s) / array_expr(s) @@ -693,9 +736,8 @@ ObjExtend( el!(Obj(ObjBody::MemberList(vec![])), 0, 2), ObjBody::MemberList(vec![ - Member::BindStmt(BindSpec { - name: "x".into(), - params: None, + Member::BindStmt(BindSpec::Field { + into: Destruct::Full("x".into()), value: el!(Num(1.0), 15, 16) }), Member::Field(FieldMember {