difftreelog
feat simplify Thunk creation with closure syntax
in: master
9 files changed
crates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -7,7 +7,7 @@
use super::ArrValue;
use crate::{
error::ErrorKind::InfiniteRecursionDetected, evaluate, function::FuncVal, typed::Typed,
- val::ThunkValue, Context, Error, ObjValue, Result, Thunk, Val,
+ Context, Error, ObjValue, Result, Thunk, Val,
};
pub trait ArrayLike: Any + Trace + Debug {
@@ -182,23 +182,6 @@
Ok(Some(new_value))
}
fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
- #[derive(Trace)]
- struct ArrayElement {
- arr_thunk: ExprArray,
- index: usize,
- }
-
- impl ThunkValue for ArrayElement {
- type Output = Val;
-
- fn get(self: Box<Self>) -> Result<Self::Output> {
- self.arr_thunk
- .get(self.index)
- .transpose()
- .expect("index checked")
- }
- }
-
if index >= self.len() {
return None;
}
@@ -208,9 +191,9 @@
ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}
};
- Some(Thunk::new(ArrayElement {
- arr_thunk: self.clone(),
- index,
+ let arr_thunk = self.clone();
+ Some(Thunk!(move || {
+ arr_thunk.get(index).transpose().expect("index checked")
}))
}
fn get_cheap(&self, _index: usize) -> Option<Val> {
@@ -492,23 +475,6 @@
Ok(Some(new_value))
}
fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
- #[derive(Trace)]
- struct ArrayElement<const WITH_INDEX: bool> {
- arr_thunk: MappedArray<WITH_INDEX>,
- index: usize,
- }
-
- impl<const WITH_INDEX: bool> ThunkValue for ArrayElement<WITH_INDEX> {
- type Output = Val;
-
- fn get(self: Box<Self>) -> Result<Self::Output> {
- self.arr_thunk
- .get(self.index)
- .transpose()
- .expect("index checked")
- }
- }
-
if index >= self.len() {
return None;
}
@@ -518,9 +484,9 @@
ArrayThunk::Waiting(()) | ArrayThunk::Pending => {}
};
- Some(Thunk::new(ArrayElement {
- arr_thunk: self.clone(),
- index,
+ let arr_thunk = self.clone();
+ Some(Thunk!(move || {
+ arr_thunk.get(index).transpose().expect("index checked")
}))
}
crates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -1,13 +1,11 @@
-use jrsonnet_gcmodule::Trace;
use jrsonnet_interner::IStr;
-use jrsonnet_parser::{BindSpec, Destruct, LocExpr, ParamsDesc};
+use jrsonnet_parser::{BindSpec, Destruct};
use crate::{
bail,
error::{ErrorKind::*, Result},
evaluate, evaluate_method, evaluate_named,
gc::GcHashMap,
- val::ThunkValue,
Context, Pending, Thunk, Val,
};
@@ -31,65 +29,34 @@
#[cfg(feature = "exp-destruct")]
Destruct::Array { start, rest, end } => {
use jrsonnet_parser::DestructRest;
-
- use crate::arr::ArrValue;
-
- #[derive(Trace)]
- struct DataThunk {
- parent: Thunk<Val>,
- min_len: usize,
- has_rest: bool,
- }
- impl ThunkValue for DataThunk {
- type Output = ArrValue;
- fn get(self: Box<Self>) -> Result<Self::Output> {
- let v = self.parent.evaluate()?;
- let Val::Arr(arr) = v else {
- bail!("expected array");
- };
- if !self.has_rest {
- if arr.len() != self.min_len {
- bail!("expected {} elements, got {}", self.min_len, arr.len())
- }
- } else if arr.len() < self.min_len {
- bail!(
- "expected at least {} elements, but array was only {}",
- self.min_len,
- arr.len()
- )
+ let min_len = start.len() + end.len();
+ let has_rest = rest.is_some();
+ let full = Thunk!(move || {
+ let v = parent.evaluate()?;
+ let Val::Arr(arr) = v else {
+ bail!("expected array");
+ };
+ if !has_rest {
+ if arr.len() != min_len {
+ bail!("expected {} elements, got {}", min_len, arr.len())
}
- Ok(arr)
+ } else if arr.len() < min_len {
+ bail!(
+ "expected at least {} elements, but array was only {}",
+ min_len,
+ arr.len()
+ )
}
- }
-
- let full = Thunk::new(DataThunk {
- min_len: start.len() + end.len(),
- has_rest: rest.is_some(),
- parent,
+ Ok(arr)
});
{
- #[derive(Trace)]
- struct BaseThunk {
- full: Thunk<ArrValue>,
- index: usize,
- }
- impl ThunkValue for BaseThunk {
- type Output = Val;
-
- fn get(self: Box<Self>) -> Result<Self::Output> {
- let full = self.full.evaluate()?;
- Ok(full.get(self.index)?.expect("length is checked"))
- }
- }
for (i, d) in start.iter().enumerate() {
+ let full = full.clone();
destruct(
d,
- Thunk::new(BaseThunk {
- full: full.clone(),
- index: i,
- }),
+ Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))),
fctx.clone(),
new_bindings,
)?;
@@ -98,32 +65,19 @@
match rest {
Some(DestructRest::Keep(v)) => {
- #[derive(Trace)]
- struct RestThunk {
- full: Thunk<ArrValue>,
- start: usize,
- end: usize,
- }
- impl ThunkValue for RestThunk {
- type Output = Val;
-
- fn get(self: Box<Self>) -> Result<Self::Output> {
- let full = self.full.evaluate()?;
- let to = full.len() - self.end;
+ let start = start.len();
+ let end = end.len();
+ let full = full.clone();
+ destruct(
+ &Destruct::Full(v.clone()),
+ Thunk!(move || {
+ let full = full.evaluate()?;
+ let to = full.len() - end;
Ok(Val::Arr(full.slice(
- Some(self.start as i32),
+ Some(start as i32),
Some(to as i32),
None,
)))
- }
- }
-
- destruct(
- &Destruct::Full(v.clone()),
- Thunk::new(RestThunk {
- full: full.clone(),
- start: start.len(),
- end: end.len(),
}),
fctx.clone(),
new_bindings,
@@ -133,29 +87,14 @@
}
{
- #[derive(Trace)]
- struct EndThunk {
- full: Thunk<ArrValue>,
- index: usize,
- end: usize,
- }
- impl ThunkValue for EndThunk {
- type Output = Val;
-
- fn get(self: Box<Self>) -> Result<Self::Output> {
- let full = self.full.evaluate()?;
- Ok(full
- .get(full.len() - self.end + self.index)?
- .expect("length is checked"))
- }
- }
for (i, d) in end.iter().enumerate() {
+ let full = full.clone();
+ let end = end.len();
destruct(
d,
- Thunk::new(EndThunk {
- full: full.clone(),
- index: i,
- end: end.len(),
+ Thunk!(move || {
+ let full = full.evaluate()?;
+ Ok(full.get(full.len() - end + i)?.expect("length is checked"))
}),
fctx.clone(),
new_bindings,
@@ -165,71 +104,46 @@
}
#[cfg(feature = "exp-destruct")]
Destruct::Object { fields, rest } => {
- use crate::obj::ObjValue;
-
- #[derive(Trace)]
- struct DataThunk {
- parent: Thunk<Val>,
- field_names: Vec<(IStr, bool)>,
- has_rest: bool,
- }
- impl ThunkValue for DataThunk {
- type Output = ObjValue;
-
- fn get(self: Box<Self>) -> Result<Self::Output> {
- let v = self.parent.evaluate()?;
- let Val::Obj(obj) = v else {
- bail!("expected object");
- };
- for (field, has_default) in &self.field_names {
- if !has_default && !obj.has_field_ex(field.clone(), true) {
- bail!("missing field: {field}");
- }
- }
- if !self.has_rest {
- let len = obj.len();
- if len > self.field_names.len() {
- bail!("too many fields, and rest not found");
- }
- }
- Ok(obj)
- }
- }
let field_names: Vec<_> = fields
.iter()
.map(|f| (f.0.clone(), f.2.is_some()))
.collect();
- let full = Thunk::new(DataThunk {
- parent,
- field_names,
- has_rest: rest.is_some(),
+ let has_rest = rest.is_some();
+ let full = Thunk!(move || {
+ let v = parent.evaluate()?;
+ let Val::Obj(obj) = v else {
+ bail!("expected object");
+ };
+ for (field, has_default) in &field_names {
+ if !has_default && !obj.has_field_ex(field.clone(), true) {
+ bail!("missing field: {field}");
+ }
+ }
+ if !has_rest {
+ let len = obj.len();
+ if len > field_names.len() {
+ bail!("too many fields, and rest not found");
+ }
+ }
+ Ok(obj)
});
for (field, d, default) in fields {
- #[derive(Trace)]
- struct FieldThunk {
- full: Thunk<ObjValue>,
- field: IStr,
- default: Option<(Pending<Context>, LocExpr)>,
- }
- impl ThunkValue for FieldThunk {
- type Output = Val;
-
- fn get(self: Box<Self>) -> Result<Self::Output> {
- let full = self.full.evaluate()?;
- if let Some(field) = full.get(self.field)? {
+ let default = default.clone().map(|e| (fctx.clone(), e));
+ let value = {
+ let field = field.clone();
+ let full = full.clone();
+ Thunk!(move || {
+ let full = full.evaluate()?;
+ if let Some(field) = full.get(field)? {
Ok(field)
} else {
- let (fctx, expr) = self.default.as_ref().expect("shape is checked");
+ let (fctx, expr) = default.as_ref().expect("shape is checked");
Ok(evaluate(fctx.clone().unwrap(), expr)?)
}
- }
- }
- let value = Thunk::new(FieldThunk {
- full: full.clone(),
- field: field.clone(),
- default: default.clone().map(|e| (fctx.clone(), e)),
- });
+ })
+ };
+
if let Some(d) = d {
destruct(d, value, fctx.clone(), new_bindings)?;
} else {
@@ -253,26 +167,15 @@
) -> Result<()> {
match d {
BindSpec::Field { into, value } => {
- #[derive(Trace)]
- struct EvaluateThunkValue {
- name: Option<IStr>,
- fctx: Pending<Context>,
- expr: LocExpr,
- }
- impl ThunkValue for EvaluateThunkValue {
- type Output = Val;
- fn get(self: Box<Self>) -> Result<Self::Output> {
- self.name.map_or_else(
- || evaluate(self.fctx.unwrap(), &self.expr),
- |name| evaluate_named(self.fctx.unwrap(), &self.expr, name),
- )
- }
- }
- let data = Thunk::new(EvaluateThunkValue {
- name: into.name(),
- fctx: fctx.clone(),
- expr: value.clone(),
- });
+ let name = into.name();
+ let value = value.clone();
+ let data = {
+ let fctx = fctx.clone();
+ Thunk!(move || name.map_or_else(
+ || evaluate(fctx.unwrap(), &value),
+ |name| evaluate_named(fctx.unwrap(), &value, name),
+ ))
+ };
destruct(into, data, fctx, new_bindings)?;
}
BindSpec::Function {
@@ -280,37 +183,15 @@
params,
value,
} => {
- #[derive(Trace)]
- struct MethodThunk {
- fctx: Pending<Context>,
- name: IStr,
- params: ParamsDesc,
- value: LocExpr,
- }
- impl ThunkValue for MethodThunk {
- type Output = Val;
-
- fn get(self: Box<Self>) -> Result<Self::Output> {
- Ok(evaluate_method(
- self.fctx.unwrap(),
- self.name,
- self.params,
- self.value,
- ))
- }
- }
-
- let old = new_bindings.insert(
- name.clone(),
- Thunk::new(MethodThunk {
- fctx,
- name: name.clone(),
- params: params.clone(),
- value: value.clone(),
- }),
- );
+ let params = params.clone();
+ let name = name.clone();
+ let value = value.clone();
+ let old = new_bindings.insert(name.clone(), {
+ let name = name.clone();
+ Thunk!(move || Ok(evaluate_method(fctx.unwrap(), name, params, value)))
+ });
if old.is_some() {
- bail!(DuplicateLocalVar(name.clone()))
+ bail!(DuplicateLocalVar(name))
}
}
}
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -18,7 +18,7 @@
function::{CallLocation, FuncDesc, FuncVal},
in_frame,
typed::Typed,
- val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk, ThunkValue},
+ val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk},
Context, Error, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,
ResultExt, Unbound, Val,
};
@@ -139,29 +139,14 @@
#[cfg(feature = "exp-preserve-order")]
false,
) {
- #[derive(Trace)]
- struct ObjectFieldThunk {
- obj: ObjValue,
- field: IStr,
- }
- impl ThunkValue for ObjectFieldThunk {
- type Output = Val;
-
- fn get(self: Box<Self>) -> Result<Self::Output> {
- self.obj.get(self.field).transpose().expect(
- "field exists, as field name was obtained from object.fields()",
- )
- }
- }
-
let fctx = Pending::new();
let mut new_bindings = GcHashMap::with_capacity(var.capacity_hint());
+ let obj = obj.clone();
let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![
Thunk::evaluated(Val::string(field.clone())),
- Thunk::new(ObjectFieldThunk {
- field: field.clone(),
- obj: obj.clone(),
- }),
+ Thunk!(move || obj.get(field).transpose().expect(
+ "field exists, as field name was obtained from object.fields()",
+ )),
])));
destruct(var, value, fctx.clone(), &mut new_bindings)?;
let ctx = ctx
@@ -609,21 +594,8 @@
if items.is_empty() {
Val::Arr(ArrValue::empty())
} else if items.len() == 1 {
- #[derive(Trace)]
- struct ArrayElement {
- ctx: Context,
- item: LocExpr,
- }
- impl ThunkValue for ArrayElement {
- type Output = Val;
- fn get(self: Box<Self>) -> Result<Val> {
- evaluate(self.ctx, &self.item)
- }
- }
- Val::Arr(ArrValue::lazy(vec![Thunk::new(ArrayElement {
- ctx,
- item: items[0].clone(),
- })]))
+ let item = items[0].clone();
+ Val::Arr(ArrValue::lazy(vec![Thunk!(move || evaluate(ctx, &item))]))
} else {
Val::Arr(ArrValue::expr(ctx, items.iter().cloned()))
}
@@ -631,21 +603,8 @@
ArrComp(expr, comp_specs) => {
let mut out = Vec::new();
evaluate_comp(ctx, comp_specs, &mut |ctx| {
- #[derive(Trace)]
- struct EvaluateThunk {
- ctx: Context,
- expr: LocExpr,
- }
- impl ThunkValue for EvaluateThunk {
- type Output = Val;
- fn get(self: Box<Self>) -> Result<Val> {
- evaluate(self.ctx, &self.expr)
- }
- }
- out.push(Thunk::new(EvaluateThunk {
- ctx,
- expr: expr.clone(),
- }));
+ let expr = expr.clone();
+ out.push(Thunk!(move || evaluate(ctx, &expr)));
Ok(())
})?;
Val::Arr(ArrValue::lazy(out))
crates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -90,7 +90,8 @@
let fctx = Context::new_future();
let mut defaults = GcHashMap::with_capacity(
params.iter().map(|p| p.0.capacity_hint()).sum::<usize>()
- - filled_named - filled_positionals,
+ - filled_named
+ - filled_positionals,
);
for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {
@@ -232,22 +233,6 @@
/// Creates Context, which has all argument default values applied
/// and with unbound values causing error to be returned
pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {
- #[derive(Trace)]
- struct DependsOnUnbound(IStr, ParamsDesc);
- impl ThunkValue for DependsOnUnbound {
- type Output = Val;
- fn get(self: Box<Self>) -> Result<Val> {
- Err(FunctionParameterNotBoundInCall(
- Some(self.0.clone()),
- self.1
- .iter()
- .map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some())))
- .collect(),
- )
- .into())
- }
- }
-
let fctx = Context::new_future();
let mut bindings = GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());
@@ -267,10 +252,18 @@
} else {
destruct(
¶m.0,
- Thunk::new(DependsOnUnbound(
- param.0.name().unwrap_or_else(|| "<destruct>".into()),
- params.clone(),
- )),
+ {
+ let param_name = param.0.name().unwrap_or_else(|| "<destruct>".into());
+ let params = params.clone();
+ Thunk!(move || Err(FunctionParameterNotBoundInCall(
+ Some(param_name),
+ params
+ .iter()
+ .map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some())))
+ .collect(),
+ )
+ .into()))
+ },
fctx.clone(),
&mut bindings,
)?;
crates/jrsonnet-evaluator/src/gc.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/gc.rs
+++ b/crates/jrsonnet-evaluator/src/gc.rs
@@ -158,3 +158,5 @@
Self::new()
}
}
+
+pub fn assert_trace<T: Trace>(_v: &T) {}
crates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -20,7 +20,7 @@
in_frame,
operator::evaluate_add_op,
tb,
- val::{ArrValue, ThunkValue},
+ val::ArrValue,
MaybeUnbound, Result, Thunk, Unbound, Val,
};
@@ -444,45 +444,16 @@
})
}
pub fn get_lazy(&self, key: IStr) -> Option<Thunk<Val>> {
- #[derive(Trace)]
- struct ThunkGet {
- obj: ObjValue,
- key: IStr,
- }
- impl ThunkValue for ThunkGet {
- type Output = Val;
-
- fn get(self: Box<Self>) -> Result<Self::Output> {
- Ok(self.obj.get(self.key)?.expect("field exists"))
- }
- }
-
if !self.has_field_ex(key.clone(), true) {
return None;
}
- Some(Thunk::new(ThunkGet {
- obj: self.clone(),
- key,
- }))
+ let obj = self.clone();
+
+ Some(Thunk!(move || Ok(obj.get(key)?.expect("field exists"))))
}
pub fn get_lazy_or_bail(&self, key: IStr) -> Thunk<Val> {
- #[derive(Trace)]
- struct ThunkGet {
- obj: ObjValue,
- key: IStr,
- }
- impl ThunkValue for ThunkGet {
- type Output = Val;
-
- fn get(self: Box<Self>) -> Result<Self::Output> {
- self.obj.get_or_bail(self.key)
- }
- }
-
- Thunk::new(ThunkGet {
- obj: self.clone(),
- key,
- })
+ let obj = self.clone();
+ Thunk!(move || obj.get_or_bail(key))
}
pub fn ptr_eq(a: &Self, b: &Self) -> bool {
Cc::ptr_eq(&a.0, &b.0)
@@ -733,11 +704,10 @@
self.value_cache
.borrow_mut()
.insert(cache_key.clone(), CacheValue::Pending);
- let value = self.get_for_uncached(key, this).map_err(|e| {
+ let value = self.get_for_uncached(key, this).inspect_err(|e| {
self.value_cache
.borrow_mut()
.insert(cache_key.clone(), CacheValue::Errored(e.clone()));
- e
})?;
self.value_cache.borrow_mut().insert(
cache_key,
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use std::{2 cell::RefCell,3 cmp::Ordering,4 fmt::{self, Debug, Display},5 mem::replace,6 num::NonZeroU32,7 ops::Deref,8 rc::Rc,9};1011use derivative::Derivative;12use jrsonnet_gcmodule::{Cc, Trace};13use jrsonnet_interner::IStr;14use jrsonnet_types::ValType;15use thiserror::Error;1617pub use crate::arr::{ArrValue, ArrayLike};18use crate::{19 bail,20 error::{Error, ErrorKind::*},21 function::FuncVal,22 gc::{GcHashMap, TraceBox},23 manifest::{ManifestFormat, ToStringFormat},24 tb,25 typed::BoundedUsize,26 ObjValue, Result, Unbound, WeakObjValue,27};2829pub trait ThunkValue: Trace {30 type Output;31 fn get(self: Box<Self>) -> Result<Self::Output>;32}3334#[derive(Trace)]35enum ThunkInner<T: Trace> {36 Computed(T),37 Errored(Error),38 Waiting(TraceBox<dyn ThunkValue<Output = T>>),39 Pending,40}4142/// Lazily evaluated value43#[allow(clippy::module_name_repetitions)]44#[derive(Clone, Trace)]45pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4647impl<T: Trace> Thunk<T> {48 pub fn evaluated(val: T) -> Self {49 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))50 }51 pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {52 Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))53 }54 pub fn errored(e: Error) -> Self {55 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))56 }57 pub fn result(res: Result<T, Error>) -> Self {58 match res {59 Ok(o) => Self::evaluated(o),60 Err(e) => Self::errored(e),61 }62 }63}6465impl<T> Thunk<T>66where67 T: Clone + Trace,68{69 pub fn force(&self) -> Result<()> {70 self.evaluate()?;71 Ok(())72 }7374 /// Evaluate thunk, or return cached value75 ///76 /// # Errors77 ///78 /// - Lazy value evaluation returned error79 /// - This method was called during inner value evaluation80 pub fn evaluate(&self) -> Result<T> {81 match &*self.0.borrow() {82 ThunkInner::Computed(v) => return Ok(v.clone()),83 ThunkInner::Errored(e) => return Err(e.clone()),84 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),85 ThunkInner::Waiting(..) => (),86 };87 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)88 else {89 unreachable!();90 };91 let new_value = match value.0.get() {92 Ok(v) => v,93 Err(e) => {94 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());95 return Err(e);96 }97 };98 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());99 Ok(new_value)100 }101}102103pub trait ThunkMapper<Input>: Trace {104 type Output;105 fn map(self, from: Input) -> Result<Self::Output>;106}107impl<Input> Thunk<Input>108where109 Input: Trace + Clone,110{111 pub fn map<M>(self, mapper: M) -> Thunk<M::Output>112 where113 M: ThunkMapper<Input>,114 M::Output: Trace,115 {116 #[derive(Trace)]117 struct Mapped<Input: Trace, Mapper: Trace> {118 inner: Thunk<Input>,119 mapper: Mapper,120 }121 impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>122 where123 Input: Trace + Clone,124 Mapper: ThunkMapper<Input>,125 {126 type Output = Mapper::Output;127128 fn get(self: Box<Self>) -> Result<Self::Output> {129 let value = self.inner.evaluate()?;130 let mapped = self.mapper.map(value)?;131 Ok(mapped)132 }133 }134135 Thunk::new(Mapped::<Input, M> {136 inner: self,137 mapper,138 })139 }140}141142impl<T: Trace> From<Result<T>> for Thunk<T> {143 fn from(value: Result<T>) -> Self {144 match value {145 Ok(o) => Self::evaluated(o),146 Err(e) => Self::errored(e),147 }148 }149}150impl<T, V: Trace> From<T> for Thunk<V>151where152 T: ThunkValue<Output = V>,153{154 fn from(value: T) -> Self {155 Self::new(value)156 }157}158159impl<T: Trace + Default> Default for Thunk<T> {160 fn default() -> Self {161 Self::evaluated(T::default())162 }163}164165type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);166167#[derive(Trace, Clone)]168pub struct CachedUnbound<I, T>169where170 I: Unbound<Bound = T>,171 T: Trace,172{173 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,174 value: I,175}176impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {177 pub fn new(value: I) -> Self {178 Self {179 cache: Cc::new(RefCell::new(GcHashMap::new())),180 value,181 }182 }183}184impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {185 type Bound = T;186 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {187 let cache_key = (188 sup.as_ref().map(|s| s.clone().downgrade()),189 this.as_ref().map(|t| t.clone().downgrade()),190 );191 {192 if let Some(t) = self.cache.borrow().get(&cache_key) {193 return Ok(t.clone());194 }195 }196 let bound = self.value.bind(sup, this)?;197198 {199 let mut cache = self.cache.borrow_mut();200 cache.insert(cache_key, bound.clone());201 }202203 Ok(bound)204 }205}206207impl<T: Debug + Trace> Debug for Thunk<T> {208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {209 write!(f, "Lazy")210 }211}212impl<T: Trace> PartialEq for Thunk<T> {213 fn eq(&self, other: &Self) -> bool {214 Cc::ptr_eq(&self.0, &other.0)215 }216}217218/// Represents a Jsonnet value, which can be sliced or indexed (string or array).219#[allow(clippy::module_name_repetitions)]220pub enum IndexableVal {221 /// String.222 Str(IStr),223 /// Array.224 Arr(ArrValue),225}226impl IndexableVal {227 pub fn is_empty(&self) -> bool {228 match self {229 Self::Str(s) => s.is_empty(),230 Self::Arr(s) => s.is_empty(),231 }232 }233234 pub fn to_array(self) -> ArrValue {235 match self {236 Self::Str(s) => ArrValue::chars(s.chars()),237 Self::Arr(arr) => arr,238 }239 }240 /// Slice the value.241 ///242 /// # Implementation243 ///244 /// For strings, will create a copy of specified interval.245 ///246 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.247 pub fn slice(248 self,249 index: Option<i32>,250 end: Option<i32>,251 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,252 ) -> Result<Self> {253 match &self {254 Self::Str(s) => {255 let mut computed_len = None;256 let mut get_len = || {257 computed_len.map_or_else(258 || {259 let len = s.chars().count();260 let _ = computed_len.insert(len);261 len262 },263 |len| len,264 )265 };266 let mut get_idx = |pos: Option<i32>, default| {267 match pos {268 Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),269 // No need to clamp, as iterator interface is used270 Some(v) => v as usize,271 None => default,272 }273 };274275 let index = get_idx(index, 0);276 let end = get_idx(end, usize::MAX);277 let step = step.as_deref().copied().unwrap_or(1);278279 if index >= end {280 return Ok(Self::Str("".into()));281 }282283 Ok(Self::Str(284 (s.chars()285 .skip(index)286 .take(end - index)287 .step_by(step)288 .collect::<String>())289 .into(),290 ))291 }292 Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(293 index,294 end,295 step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),296 ))),297 }298 }299}300301#[derive(Debug, Clone, Trace)]302pub enum StrValue {303 Flat(IStr),304 Tree(Rc<(StrValue, StrValue, usize)>),305}306impl StrValue {307 pub fn concat(a: Self, b: Self) -> Self {308 // TODO: benchmark for an optimal value, currently just a arbitrary choice309 const STRING_EXTEND_THRESHOLD: usize = 100;310311 if a.is_empty() {312 b313 } else if b.is_empty() {314 a315 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {316 Self::Flat(format!("{a}{b}").into())317 } else {318 let len = a.len() + b.len();319 Self::Tree(Rc::new((a, b, len)))320 }321 }322 pub fn into_flat(self) -> IStr {323 #[cold]324 fn write_buf(s: &StrValue, out: &mut String) {325 match s {326 StrValue::Flat(f) => out.push_str(f),327 StrValue::Tree(t) => {328 write_buf(&t.0, out);329 write_buf(&t.1, out);330 }331 }332 }333 match self {334 Self::Flat(f) => f,335 Self::Tree(_) => {336 let mut buf = String::with_capacity(self.len());337 write_buf(&self, &mut buf);338 buf.into()339 }340 }341 }342 pub fn len(&self) -> usize {343 match self {344 Self::Flat(v) => v.len(),345 Self::Tree(t) => t.2,346 }347 }348 pub fn is_empty(&self) -> bool {349 match self {350 Self::Flat(v) => v.is_empty(),351 // Can't create non-flat empty string352 Self::Tree(_) => false,353 }354 }355}356impl<T> From<T> for StrValue357where358 IStr: From<T>,359{360 fn from(value: T) -> Self {361 Self::Flat(IStr::from(value))362 }363}364impl Display for StrValue {365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {366 match self {367 Self::Flat(v) => write!(f, "{v}"),368 Self::Tree(t) => {369 write!(f, "{}", t.0)?;370 write!(f, "{}", t.1)371 }372 }373 }374}375impl PartialEq for StrValue {376 // False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.377 #[allow(clippy::unconditional_recursion)]378 fn eq(&self, other: &Self) -> bool {379 let a = self.clone().into_flat();380 let b = other.clone().into_flat();381 a == b382 }383}384impl Eq for StrValue {}385impl PartialOrd for StrValue {386 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {387 Some(self.cmp(other))388 }389}390impl Ord for StrValue {391 fn cmp(&self, other: &Self) -> Ordering {392 let a = self.clone().into_flat();393 let b = other.clone().into_flat();394 a.cmp(&b)395 }396}397398/// Represents jsonnet number399/// Jsonnet numbers are finite f64, with NaNs disallowed400#[derive(Trace, Clone, Copy, Derivative)]401#[derivative(Debug = "transparent")]402#[repr(transparent)]403pub struct NumValue(f64);404impl NumValue {405 /// Creates a [`NumValue`], if value is finite and not NaN406 pub fn new(v: f64) -> Option<Self> {407 if !v.is_finite() {408 return None;409 }410 Some(Self(v))411 }412 #[inline]413 pub const fn get(&self) -> f64 {414 self.0415 }416}417impl PartialEq for NumValue {418 fn eq(&self, other: &Self) -> bool {419 self.0 == other.0420 }421}422impl Eq for NumValue {}423impl Ord for NumValue {424 #[inline]425 fn cmp(&self, other: &Self) -> Ordering {426 // Can't use `total_cmp`: its behavior for `-0` and `0`427 // is not following wanted.428 unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }429 }430}431impl PartialOrd for NumValue {432 #[inline]433 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {434 Some(self.cmp(other))435 }436}437impl Display for NumValue {438 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {439 Display::fmt(&self.0, f)440 }441}442impl Deref for NumValue {443 type Target = f64;444445 #[inline]446 fn deref(&self) -> &Self::Target {447 &self.0448 }449}450macro_rules! impl_num {451 ($($ty:ty),+) => {$(452 impl From<$ty> for NumValue {453 #[inline]454 fn from(value: $ty) -> Self {455 Self(value.into())456 }457 }458 )+};459}460impl_num!(i8, u8, i16, u16, i32, u32);461462#[derive(Clone, Copy, Debug, Error, Trace)]463pub enum ConvertNumValueError {464 #[error("overflow")]465 Overflow,466 #[error("underflow")]467 Underflow,468 #[error("non-finite")]469 NonFinite,470}471impl From<ConvertNumValueError> for Error {472 fn from(e: ConvertNumValueError) -> Self {473 Self::new(e.into())474 }475}476477macro_rules! impl_try_num {478 ($($ty:ty),+) => {$(479 impl TryFrom<$ty> for NumValue {480 type Error = ConvertNumValueError;481 #[inline]482 fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {483 use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER};484 let value = value as f64;485 if value < MIN_SAFE_INTEGER {486 return Err(ConvertNumValueError::Underflow)487 } else if value > MAX_SAFE_INTEGER {488 return Err(ConvertNumValueError::Overflow)489 }490 // Number is finite.491 Ok(Self(value))492 }493 }494 )+};495}496impl_try_num!(usize, isize, i64, u64);497498impl TryFrom<f64> for NumValue {499 type Error = ConvertNumValueError;500501 #[inline]502 fn try_from(value: f64) -> Result<Self, Self::Error> {503 Self::new(value).ok_or(ConvertNumValueError::NonFinite)504 }505}506impl TryFrom<f32> for NumValue {507 type Error = ConvertNumValueError;508509 #[inline]510 fn try_from(value: f32) -> Result<Self, Self::Error> {511 Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)512 }513}514515/// Represents any valid Jsonnet value.516#[derive(Debug, Clone, Trace, Default)]517pub enum Val {518 /// Represents a Jsonnet boolean.519 Bool(bool),520 /// Represents a Jsonnet null value.521 #[default]522 Null,523 /// Represents a Jsonnet string.524 Str(StrValue),525 /// Represents a Jsonnet number.526 /// Should be finite, and not NaN527 /// This restriction isn't enforced by enum, as enum field can't be marked as private528 Num(NumValue),529 /// Experimental bigint530 #[cfg(feature = "exp-bigint")]531 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),532 /// Represents a Jsonnet array.533 Arr(ArrValue),534 /// Represents a Jsonnet object.535 Obj(ObjValue),536 /// Represents a Jsonnet function.537 Func(FuncVal),538}539540#[cfg(target_pointer_width = "64")]541static_assertions::assert_eq_size!(Val, [u8; 24]);542543impl From<IndexableVal> for Val {544 fn from(v: IndexableVal) -> Self {545 match v {546 IndexableVal::Str(s) => Self::string(s),547 IndexableVal::Arr(a) => Self::Arr(a),548 }549 }550}551552impl Val {553 pub const fn as_bool(&self) -> Option<bool> {554 match self {555 Self::Bool(v) => Some(*v),556 _ => None,557 }558 }559 pub const fn as_null(&self) -> Option<()> {560 match self {561 Self::Null => Some(()),562 _ => None,563 }564 }565 pub fn as_str(&self) -> Option<IStr> {566 match self {567 Self::Str(s) => Some(s.clone().into_flat()),568 _ => None,569 }570 }571 pub const fn as_num(&self) -> Option<f64> {572 match self {573 Self::Num(n) => Some(n.get()),574 _ => None,575 }576 }577 pub fn as_arr(&self) -> Option<ArrValue> {578 match self {579 Self::Arr(a) => Some(a.clone()),580 _ => None,581 }582 }583 pub fn as_obj(&self) -> Option<ObjValue> {584 match self {585 Self::Obj(o) => Some(o.clone()),586 _ => None,587 }588 }589 pub fn as_func(&self) -> Option<FuncVal> {590 match self {591 Self::Func(f) => Some(f.clone()),592 _ => None,593 }594 }595596 pub const fn value_type(&self) -> ValType {597 match self {598 Self::Str(..) => ValType::Str,599 Self::Num(..) => ValType::Num,600 #[cfg(feature = "exp-bigint")]601 Self::BigInt(..) => ValType::BigInt,602 Self::Arr(..) => ValType::Arr,603 Self::Obj(..) => ValType::Obj,604 Self::Bool(_) => ValType::Bool,605 Self::Null => ValType::Null,606 Self::Func(..) => ValType::Func,607 }608 }609610 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {611 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {612 manifest.manifest(val.clone())613 }614 manifest_dyn(self, &format)615 }616617 pub fn to_string(&self) -> Result<IStr> {618 Ok(match self {619 Self::Bool(true) => "true".into(),620 Self::Bool(false) => "false".into(),621 Self::Null => "null".into(),622 Self::Str(s) => s.clone().into_flat(),623 _ => self.manifest(ToStringFormat).map(IStr::from)?,624 })625 }626627 pub fn into_indexable(self) -> Result<IndexableVal> {628 Ok(match self {629 Self::Str(s) => IndexableVal::Str(s.into_flat()),630 Self::Arr(arr) => IndexableVal::Arr(arr),631 _ => bail!(ValueIsNotIndexable(self.value_type())),632 })633 }634635 pub fn function(function: impl Into<FuncVal>) -> Self {636 Self::Func(function.into())637 }638 pub fn string(string: impl Into<StrValue>) -> Self {639 Self::Str(string.into())640 }641 pub fn num(num: impl Into<NumValue>) -> Self {642 Self::Num(num.into())643 }644 pub fn try_num<V, E>(num: V) -> Result<Self, E>645 where646 NumValue: TryFrom<V, Error = E>,647 {648 Ok(Self::Num(num.try_into()?))649 }650}651652impl From<IStr> for Val {653 fn from(value: IStr) -> Self {654 Self::string(value)655 }656}657impl From<String> for Val {658 fn from(value: String) -> Self {659 Self::string(value)660 }661}662impl From<&str> for Val {663 fn from(value: &str) -> Self {664 Self::string(value)665 }666}667impl From<ObjValue> for Val {668 fn from(value: ObjValue) -> Self {669 Self::Obj(value)670 }671}672673const fn is_function_like(val: &Val) -> bool {674 matches!(val, Val::Func(_))675}676677/// Native implementation of `std.primitiveEquals`678pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {679 Ok(match (val_a, val_b) {680 (Val::Bool(a), Val::Bool(b)) => a == b,681 (Val::Null, Val::Null) => true,682 (Val::Str(a), Val::Str(b)) => a == b,683 (Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,684 #[cfg(feature = "exp-bigint")]685 (Val::BigInt(a), Val::BigInt(b)) => a == b,686 (Val::Arr(_), Val::Arr(_)) => {687 bail!("primitiveEquals operates on primitive types, got array")688 }689 (Val::Obj(_), Val::Obj(_)) => {690 bail!("primitiveEquals operates on primitive types, got object")691 }692 (a, b) if is_function_like(a) && is_function_like(b) => {693 bail!("cannot test equality of functions")694 }695 (_, _) => false,696 })697}698699/// Native implementation of `std.equals`700pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {701 if val_a.value_type() != val_b.value_type() {702 return Ok(false);703 }704 match (val_a, val_b) {705 (Val::Arr(a), Val::Arr(b)) => {706 if ArrValue::ptr_eq(a, b) {707 return Ok(true);708 }709 if a.len() != b.len() {710 return Ok(false);711 }712 for (a, b) in a.iter().zip(b.iter()) {713 if !equals(&a?, &b?)? {714 return Ok(false);715 }716 }717 Ok(true)718 }719 (Val::Obj(a), Val::Obj(b)) => {720 if ObjValue::ptr_eq(a, b) {721 return Ok(true);722 }723 let fields = a.fields(724 #[cfg(feature = "exp-preserve-order")]725 false,726 );727 if fields728 != b.fields(729 #[cfg(feature = "exp-preserve-order")]730 false,731 ) {732 return Ok(false);733 }734 for field in fields {735 if !equals(736 &a.get(field.clone())?.expect("field exists"),737 &b.get(field)?.expect("field exists"),738 )? {739 return Ok(false);740 }741 }742 Ok(true)743 }744 (a, b) => Ok(primitive_equals(a, b)?),745 }746}1use std::{2 cell::RefCell,3 cmp::Ordering,4 fmt::{self, Debug, Display},5 mem::replace,6 num::NonZeroU32,7 ops::Deref,8 rc::Rc,9};1011use derivative::Derivative;12use jrsonnet_gcmodule::{Cc, Trace};13use jrsonnet_interner::IStr;14pub use jrsonnet_macros::Thunk;15use jrsonnet_types::ValType;16use thiserror::Error;1718pub use crate::arr::{ArrValue, ArrayLike};19use crate::{20 bail,21 error::{Error, ErrorKind::*},22 function::FuncVal,23 gc::{GcHashMap, TraceBox},24 manifest::{ManifestFormat, ToStringFormat},25 tb,26 typed::BoundedUsize,27 ObjValue, Result, Unbound, WeakObjValue,28};2930pub trait ThunkValue: Trace {31 type Output;32 fn get(self: Box<Self>) -> Result<Self::Output>;33}3435#[derive(Trace)]36pub struct ThunkValueClosure<D: Trace, O: 'static> {37 env: D,38 // Carries no data, as it is not a real closure, all the39 // captured environment is stored in `env` field.40 #[trace(skip)]41 closure: fn(D) -> Result<O>,42}43impl<D: Trace, O: 'static> ThunkValueClosure<D, O> {44 pub fn new(env: D, closure: fn(D) -> Result<O>) -> Self {45 Self { env, closure }46 }47}48impl<D: Trace, O: 'static> ThunkValue for ThunkValueClosure<D, O> {49 type Output = O;5051 fn get(self: Box<Self>) -> Result<Self::Output> {52 (self.closure)(self.env)53 }54}5556#[derive(Trace)]57enum ThunkInner<T: Trace> {58 Computed(T),59 Errored(Error),60 Waiting(TraceBox<dyn ThunkValue<Output = T>>),61 Pending,62}6364/// Lazily evaluated value65#[allow(clippy::module_name_repetitions)]66#[derive(Clone, Trace)]67pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);6869impl<T: Trace> Thunk<T> {70 pub fn evaluated(val: T) -> Self {71 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))72 }73 pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {74 Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))75 }76 pub fn errored(e: Error) -> Self {77 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))78 }79 pub fn result(res: Result<T, Error>) -> Self {80 match res {81 Ok(o) => Self::evaluated(o),82 Err(e) => Self::errored(e),83 }84 }85}8687impl<T> Thunk<T>88where89 T: Clone + Trace,90{91 pub fn force(&self) -> Result<()> {92 self.evaluate()?;93 Ok(())94 }9596 /// Evaluate thunk, or return cached value97 ///98 /// # Errors99 ///100 /// - Lazy value evaluation returned error101 /// - This method was called during inner value evaluation102 pub fn evaluate(&self) -> Result<T> {103 match &*self.0.borrow() {104 ThunkInner::Computed(v) => return Ok(v.clone()),105 ThunkInner::Errored(e) => return Err(e.clone()),106 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),107 ThunkInner::Waiting(..) => (),108 };109 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)110 else {111 unreachable!();112 };113 let new_value = match value.0.get() {114 Ok(v) => v,115 Err(e) => {116 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());117 return Err(e);118 }119 };120 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());121 Ok(new_value)122 }123}124125pub trait ThunkMapper<Input>: Trace {126 type Output;127 fn map(self, from: Input) -> Result<Self::Output>;128}129impl<Input> Thunk<Input>130where131 Input: Trace + Clone,132{133 pub fn map<M>(self, mapper: M) -> Thunk<M::Output>134 where135 M: ThunkMapper<Input>,136 M::Output: Trace,137 {138 let inner = self;139 Thunk!(move || {140 let value = inner.evaluate()?;141 let mapped = mapper.map(value)?;142 Ok(mapped)143 })144 }145}146147impl<T: Trace> From<Result<T>> for Thunk<T> {148 fn from(value: Result<T>) -> Self {149 match value {150 Ok(o) => Self::evaluated(o),151 Err(e) => Self::errored(e),152 }153 }154}155impl<T, V: Trace> From<T> for Thunk<V>156where157 T: ThunkValue<Output = V>,158{159 fn from(value: T) -> Self {160 Self::new(value)161 }162}163164impl<T: Trace + Default> Default for Thunk<T> {165 fn default() -> Self {166 Self::evaluated(T::default())167 }168}169170type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);171172#[derive(Trace, Clone)]173pub struct CachedUnbound<I, T>174where175 I: Unbound<Bound = T>,176 T: Trace,177{178 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,179 value: I,180}181impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {182 pub fn new(value: I) -> Self {183 Self {184 cache: Cc::new(RefCell::new(GcHashMap::new())),185 value,186 }187 }188}189impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {190 type Bound = T;191 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {192 let cache_key = (193 sup.as_ref().map(|s| s.clone().downgrade()),194 this.as_ref().map(|t| t.clone().downgrade()),195 );196 {197 if let Some(t) = self.cache.borrow().get(&cache_key) {198 return Ok(t.clone());199 }200 }201 let bound = self.value.bind(sup, this)?;202203 {204 let mut cache = self.cache.borrow_mut();205 cache.insert(cache_key, bound.clone());206 }207208 Ok(bound)209 }210}211212impl<T: Debug + Trace> Debug for Thunk<T> {213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {214 write!(f, "Lazy")215 }216}217impl<T: Trace> PartialEq for Thunk<T> {218 fn eq(&self, other: &Self) -> bool {219 Cc::ptr_eq(&self.0, &other.0)220 }221}222223/// Represents a Jsonnet value, which can be sliced or indexed (string or array).224#[allow(clippy::module_name_repetitions)]225pub enum IndexableVal {226 /// String.227 Str(IStr),228 /// Array.229 Arr(ArrValue),230}231impl IndexableVal {232 pub fn is_empty(&self) -> bool {233 match self {234 Self::Str(s) => s.is_empty(),235 Self::Arr(s) => s.is_empty(),236 }237 }238239 pub fn to_array(self) -> ArrValue {240 match self {241 Self::Str(s) => ArrValue::chars(s.chars()),242 Self::Arr(arr) => arr,243 }244 }245 /// Slice the value.246 ///247 /// # Implementation248 ///249 /// For strings, will create a copy of specified interval.250 ///251 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.252 pub fn slice(253 self,254 index: Option<i32>,255 end: Option<i32>,256 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,257 ) -> Result<Self> {258 match &self {259 Self::Str(s) => {260 let mut computed_len = None;261 let mut get_len = || {262 computed_len.map_or_else(263 || {264 let len = s.chars().count();265 let _ = computed_len.insert(len);266 len267 },268 |len| len,269 )270 };271 let mut get_idx = |pos: Option<i32>, default| {272 match pos {273 Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),274 // No need to clamp, as iterator interface is used275 Some(v) => v as usize,276 None => default,277 }278 };279280 let index = get_idx(index, 0);281 let end = get_idx(end, usize::MAX);282 let step = step.as_deref().copied().unwrap_or(1);283284 if index >= end {285 return Ok(Self::Str("".into()));286 }287288 Ok(Self::Str(289 (s.chars()290 .skip(index)291 .take(end - index)292 .step_by(step)293 .collect::<String>())294 .into(),295 ))296 }297 Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(298 index,299 end,300 step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),301 ))),302 }303 }304}305306#[derive(Debug, Clone, Trace)]307pub enum StrValue {308 Flat(IStr),309 Tree(Rc<(StrValue, StrValue, usize)>),310}311impl StrValue {312 pub fn concat(a: Self, b: Self) -> Self {313 // TODO: benchmark for an optimal value, currently just a arbitrary choice314 const STRING_EXTEND_THRESHOLD: usize = 100;315316 if a.is_empty() {317 b318 } else if b.is_empty() {319 a320 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {321 Self::Flat(format!("{a}{b}").into())322 } else {323 let len = a.len() + b.len();324 Self::Tree(Rc::new((a, b, len)))325 }326 }327 pub fn into_flat(self) -> IStr {328 #[cold]329 fn write_buf(s: &StrValue, out: &mut String) {330 match s {331 StrValue::Flat(f) => out.push_str(f),332 StrValue::Tree(t) => {333 write_buf(&t.0, out);334 write_buf(&t.1, out);335 }336 }337 }338 match self {339 Self::Flat(f) => f,340 Self::Tree(_) => {341 let mut buf = String::with_capacity(self.len());342 write_buf(&self, &mut buf);343 buf.into()344 }345 }346 }347 pub fn len(&self) -> usize {348 match self {349 Self::Flat(v) => v.len(),350 Self::Tree(t) => t.2,351 }352 }353 pub fn is_empty(&self) -> bool {354 match self {355 Self::Flat(v) => v.is_empty(),356 // Can't create non-flat empty string357 Self::Tree(_) => false,358 }359 }360}361impl<T> From<T> for StrValue362where363 IStr: From<T>,364{365 fn from(value: T) -> Self {366 Self::Flat(IStr::from(value))367 }368}369impl Display for StrValue {370 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {371 match self {372 Self::Flat(v) => write!(f, "{v}"),373 Self::Tree(t) => {374 write!(f, "{}", t.0)?;375 write!(f, "{}", t.1)376 }377 }378 }379}380impl PartialEq for StrValue {381 // False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.382 #[allow(clippy::unconditional_recursion)]383 fn eq(&self, other: &Self) -> bool {384 let a = self.clone().into_flat();385 let b = other.clone().into_flat();386 a == b387 }388}389impl Eq for StrValue {}390impl PartialOrd for StrValue {391 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {392 Some(self.cmp(other))393 }394}395impl Ord for StrValue {396 fn cmp(&self, other: &Self) -> Ordering {397 let a = self.clone().into_flat();398 let b = other.clone().into_flat();399 a.cmp(&b)400 }401}402403/// Represents jsonnet number404/// Jsonnet numbers are finite f64, with NaNs disallowed405#[derive(Trace, Clone, Copy, Derivative)]406#[derivative(Debug = "transparent")]407#[repr(transparent)]408pub struct NumValue(f64);409impl NumValue {410 /// Creates a [`NumValue`], if value is finite and not NaN411 pub fn new(v: f64) -> Option<Self> {412 if !v.is_finite() {413 return None;414 }415 Some(Self(v))416 }417 #[inline]418 pub const fn get(&self) -> f64 {419 self.0420 }421}422impl PartialEq for NumValue {423 fn eq(&self, other: &Self) -> bool {424 self.0 == other.0425 }426}427impl Eq for NumValue {}428impl Ord for NumValue {429 #[inline]430 fn cmp(&self, other: &Self) -> Ordering {431 // Can't use `total_cmp`: its behavior for `-0` and `0`432 // is not following wanted.433 unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }434 }435}436impl PartialOrd for NumValue {437 #[inline]438 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {439 Some(self.cmp(other))440 }441}442impl Display for NumValue {443 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {444 Display::fmt(&self.0, f)445 }446}447impl Deref for NumValue {448 type Target = f64;449450 #[inline]451 fn deref(&self) -> &Self::Target {452 &self.0453 }454}455macro_rules! impl_num {456 ($($ty:ty),+) => {$(457 impl From<$ty> for NumValue {458 #[inline]459 fn from(value: $ty) -> Self {460 Self(value.into())461 }462 }463 )+};464}465impl_num!(i8, u8, i16, u16, i32, u32);466467#[derive(Clone, Copy, Debug, Error, Trace)]468pub enum ConvertNumValueError {469 #[error("overflow")]470 Overflow,471 #[error("underflow")]472 Underflow,473 #[error("non-finite")]474 NonFinite,475}476impl From<ConvertNumValueError> for Error {477 fn from(e: ConvertNumValueError) -> Self {478 Self::new(e.into())479 }480}481482macro_rules! impl_try_num {483 ($($ty:ty),+) => {$(484 impl TryFrom<$ty> for NumValue {485 type Error = ConvertNumValueError;486 #[inline]487 fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {488 use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER};489 let value = value as f64;490 if value < MIN_SAFE_INTEGER {491 return Err(ConvertNumValueError::Underflow)492 } else if value > MAX_SAFE_INTEGER {493 return Err(ConvertNumValueError::Overflow)494 }495 // Number is finite.496 Ok(Self(value))497 }498 }499 )+};500}501impl_try_num!(usize, isize, i64, u64);502503impl TryFrom<f64> for NumValue {504 type Error = ConvertNumValueError;505506 #[inline]507 fn try_from(value: f64) -> Result<Self, Self::Error> {508 Self::new(value).ok_or(ConvertNumValueError::NonFinite)509 }510}511impl TryFrom<f32> for NumValue {512 type Error = ConvertNumValueError;513514 #[inline]515 fn try_from(value: f32) -> Result<Self, Self::Error> {516 Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)517 }518}519520/// Represents any valid Jsonnet value.521#[derive(Debug, Clone, Trace, Default)]522pub enum Val {523 /// Represents a Jsonnet boolean.524 Bool(bool),525 /// Represents a Jsonnet null value.526 #[default]527 Null,528 /// Represents a Jsonnet string.529 Str(StrValue),530 /// Represents a Jsonnet number.531 /// Should be finite, and not NaN532 /// This restriction isn't enforced by enum, as enum field can't be marked as private533 Num(NumValue),534 /// Experimental bigint535 #[cfg(feature = "exp-bigint")]536 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),537 /// Represents a Jsonnet array.538 Arr(ArrValue),539 /// Represents a Jsonnet object.540 Obj(ObjValue),541 /// Represents a Jsonnet function.542 Func(FuncVal),543}544545#[cfg(target_pointer_width = "64")]546static_assertions::assert_eq_size!(Val, [u8; 24]);547548impl From<IndexableVal> for Val {549 fn from(v: IndexableVal) -> Self {550 match v {551 IndexableVal::Str(s) => Self::string(s),552 IndexableVal::Arr(a) => Self::Arr(a),553 }554 }555}556557impl Val {558 pub const fn as_bool(&self) -> Option<bool> {559 match self {560 Self::Bool(v) => Some(*v),561 _ => None,562 }563 }564 pub const fn as_null(&self) -> Option<()> {565 match self {566 Self::Null => Some(()),567 _ => None,568 }569 }570 pub fn as_str(&self) -> Option<IStr> {571 match self {572 Self::Str(s) => Some(s.clone().into_flat()),573 _ => None,574 }575 }576 pub const fn as_num(&self) -> Option<f64> {577 match self {578 Self::Num(n) => Some(n.get()),579 _ => None,580 }581 }582 pub fn as_arr(&self) -> Option<ArrValue> {583 match self {584 Self::Arr(a) => Some(a.clone()),585 _ => None,586 }587 }588 pub fn as_obj(&self) -> Option<ObjValue> {589 match self {590 Self::Obj(o) => Some(o.clone()),591 _ => None,592 }593 }594 pub fn as_func(&self) -> Option<FuncVal> {595 match self {596 Self::Func(f) => Some(f.clone()),597 _ => None,598 }599 }600601 pub const fn value_type(&self) -> ValType {602 match self {603 Self::Str(..) => ValType::Str,604 Self::Num(..) => ValType::Num,605 #[cfg(feature = "exp-bigint")]606 Self::BigInt(..) => ValType::BigInt,607 Self::Arr(..) => ValType::Arr,608 Self::Obj(..) => ValType::Obj,609 Self::Bool(_) => ValType::Bool,610 Self::Null => ValType::Null,611 Self::Func(..) => ValType::Func,612 }613 }614615 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {616 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {617 manifest.manifest(val.clone())618 }619 manifest_dyn(self, &format)620 }621622 pub fn to_string(&self) -> Result<IStr> {623 Ok(match self {624 Self::Bool(true) => "true".into(),625 Self::Bool(false) => "false".into(),626 Self::Null => "null".into(),627 Self::Str(s) => s.clone().into_flat(),628 _ => self.manifest(ToStringFormat).map(IStr::from)?,629 })630 }631632 pub fn into_indexable(self) -> Result<IndexableVal> {633 Ok(match self {634 Self::Str(s) => IndexableVal::Str(s.into_flat()),635 Self::Arr(arr) => IndexableVal::Arr(arr),636 _ => bail!(ValueIsNotIndexable(self.value_type())),637 })638 }639640 pub fn function(function: impl Into<FuncVal>) -> Self {641 Self::Func(function.into())642 }643 pub fn string(string: impl Into<StrValue>) -> Self {644 Self::Str(string.into())645 }646 pub fn num(num: impl Into<NumValue>) -> Self {647 Self::Num(num.into())648 }649 pub fn try_num<V, E>(num: V) -> Result<Self, E>650 where651 NumValue: TryFrom<V, Error = E>,652 {653 Ok(Self::Num(num.try_into()?))654 }655}656657impl From<IStr> for Val {658 fn from(value: IStr) -> Self {659 Self::string(value)660 }661}662impl From<String> for Val {663 fn from(value: String) -> Self {664 Self::string(value)665 }666}667impl From<&str> for Val {668 fn from(value: &str) -> Self {669 Self::string(value)670 }671}672impl From<ObjValue> for Val {673 fn from(value: ObjValue) -> Self {674 Self::Obj(value)675 }676}677678const fn is_function_like(val: &Val) -> bool {679 matches!(val, Val::Func(_))680}681682/// Native implementation of `std.primitiveEquals`683pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {684 Ok(match (val_a, val_b) {685 (Val::Bool(a), Val::Bool(b)) => a == b,686 (Val::Null, Val::Null) => true,687 (Val::Str(a), Val::Str(b)) => a == b,688 (Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,689 #[cfg(feature = "exp-bigint")]690 (Val::BigInt(a), Val::BigInt(b)) => a == b,691 (Val::Arr(_), Val::Arr(_)) => {692 bail!("primitiveEquals operates on primitive types, got array")693 }694 (Val::Obj(_), Val::Obj(_)) => {695 bail!("primitiveEquals operates on primitive types, got object")696 }697 (a, b) if is_function_like(a) && is_function_like(b) => {698 bail!("cannot test equality of functions")699 }700 (_, _) => false,701 })702}703704/// Native implementation of `std.equals`705pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {706 if val_a.value_type() != val_b.value_type() {707 return Ok(false);708 }709 match (val_a, val_b) {710 (Val::Arr(a), Val::Arr(b)) => {711 if ArrValue::ptr_eq(a, b) {712 return Ok(true);713 }714 if a.len() != b.len() {715 return Ok(false);716 }717 for (a, b) in a.iter().zip(b.iter()) {718 if !equals(&a?, &b?)? {719 return Ok(false);720 }721 }722 Ok(true)723 }724 (Val::Obj(a), Val::Obj(b)) => {725 if ObjValue::ptr_eq(a, b) {726 return Ok(true);727 }728 let fields = a.fields(729 #[cfg(feature = "exp-preserve-order")]730 false,731 );732 if fields733 != b.fields(734 #[cfg(feature = "exp-preserve-order")]735 false,736 ) {737 return Ok(false);738 }739 for field in fields {740 if !equals(741 &a.get(field.clone())?.expect("field exists"),742 &b.get(field)?.expect("field exists"),743 )? {744 return Ok(false);745 }746 }747 Ok(true)748 }749 (a, b) => Ok(primitive_equals(a, b)?),750 }751}crates/jrsonnet-macros/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-macros/Cargo.toml
+++ b/crates/jrsonnet-macros/Cargo.toml
@@ -17,3 +17,4 @@
proc-macro2.workspace = true
quote.workspace = true
syn = { workspace = true, features = ["full"] }
+syn-dissect-closure.workspace = true
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -1,7 +1,7 @@
use std::string::String;
use proc_macro2::TokenStream;
-use quote::quote;
+use quote::{quote, quote_spanned};
use syn::{
parenthesized,
parse::{Parse, ParseStream},
@@ -9,8 +9,8 @@
punctuated::Punctuated,
spanned::Spanned,
token::{self, Comma},
- Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,
- PathArguments, Result, ReturnType, Token, Type,
+ Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn,
+ LitStr, Pat, Path, PathArguments, Result, ReturnType, Token, Type,
};
fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>
@@ -815,3 +815,30 @@
let input = parse_macro_input!(input as FormatInput);
input.expand().into()
}
+
+/// Create Thunk using closure syntax
+#[proc_macro]
+#[allow(non_snake_case)]
+pub fn Thunk(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let input = parse_macro_input!(input as ExprClosure);
+
+ let span = input.inputs.span();
+ let move_check = input.capture.is_none().then(|| {
+ quote_spanned! {span => {
+ compile_error!("Thunk! needs to be called with move closure");
+ }}
+ });
+
+ let (env, closure, args) = syn_dissect_closure::split_env(input);
+
+ let trace_check = args.iter().map(|el| {
+ let span = el.span();
+ quote_spanned! {span => ::jrsonnet_evaluator::gc::assert_trace(&#el);}
+ });
+
+ quote! {{
+ #move_check
+ #(#trace_check)*
+ ::jrsonnet_evaluator::Thunk::new(::jrsonnet_evaluator::val::ThunkValueClosure::new(#env, #closure))
+ }}.into()
+}