difftreelog
refactor unified ContextBuilder
in: master
17 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -699,6 +699,7 @@
"bitmaps",
"rand_core 0.6.4",
"rand_xoshiro",
+ "refpool",
"sized-chunks",
"typenum",
"version_check",
@@ -861,9 +862,9 @@
[[package]]
name = "jrsonnet-gcmodule"
-version = "0.4.3"
+version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a6a63a6e55ba82764e483d7f8a181f25db95a8f25da8ae6520e95a5fe39c6a6"
+checksum = "21dd97b40cbfb2043094219f95d96519858ba1aee4e8260eb048a1774832a517"
dependencies = [
"im-rc",
"jrsonnet-gcmodule-derive",
@@ -871,9 +872,9 @@
[[package]]
name = "jrsonnet-gcmodule-derive"
-version = "0.4.3"
+version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "095fe3c4c0acf32de80205a8a479ef63c216b9efb0024dec9eb7fe1c5ef1f1a1"
+checksum = "ede3d0445c2a7d7adab0a3cc33bdb33df78ffebebc21a2848c221526cb1795d4"
dependencies = [
"proc-macro2",
"quote",
@@ -1420,6 +1421,12 @@
]
[[package]]
+name = "refpool"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "369e86b80fa7dc8c561dd9613a5bf25c59d2d3073cd66c47fd9e39802f0ecb58"
+
+[[package]]
name = "regex"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1633,6 +1640,7 @@
checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
dependencies = [
"bitmaps",
+ "refpool",
"typenum",
]
@@ -1738,6 +1746,7 @@
"jrsonnet-evaluator",
"jrsonnet-gcmodule",
"jrsonnet-stdlib",
+ "mimallocator",
"serde",
"serde_json",
]
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,7 +22,7 @@
jrsonnet-cli = { path = "./crates/jrsonnet-cli", version = "0.5.0-pre98" }
jrsonnet-types = { path = "./crates/jrsonnet-types", version = "0.5.0-pre98" }
jrsonnet-formatter = { path = "./crates/jrsonnet-formatter", version = "0.5.0-pre98" }
-jrsonnet-gcmodule = { version = "0.4.3", features = ["im-rc"] }
+jrsonnet-gcmodule = { version = "0.4.4", features = ["im-rc"] }
# Diagnostics.
# hi-doc is my library, which handles text formatting very well, but isn't polished enough yet
# Previous implementation was based on annotate-snippets, which I don't like for many reasons.
cmds/jrsonnet/src/main.rsdiffbeforeafterboth--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -200,7 +200,7 @@
val = s.evaluate_snippet_with(
"<exp_apply>".to_owned(),
&apply,
- InitialUnderscore(Thunk::evaluated(val)),
+ &InitialUnderscore(Thunk::evaluated(val)),
)?;
}
crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -76,7 +76,7 @@
"Hash",
"PartialEq",
] }
-im-rc = "15.1.0"
+im-rc = { version = "15.1.0", features = ["pool"] }
[build-dependencies]
rustversion = "1.0.22"
crates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -3,11 +3,11 @@
use educe::Educe;
use jrsonnet_gcmodule::{Cc, Trace};
use jrsonnet_interner::IStr;
-use rustc_hash::FxHashMap;
+use rustc_hash::{FxHashMap, FxHashSet};
use crate::{
- ObjValue, Pending, Result, SupThis, Thunk, Val, error::ErrorKind::*, gc::WithCapacityExt as _,
- map::LayeredHashMap,
+ ObjValue, Pending, Result, SupThis, Thunk, Val, bail, error::ErrorKind::*,
+ gc::WithCapacityExt as _,
};
/// Context keeps information about current lexical code location
///
@@ -20,7 +20,9 @@
struct ContextInternal {
dollar: Option<ObjValue>,
sup_this: Option<SupThis>,
- bindings: LayeredHashMap,
+ bindings: FxHashMap<IStr, Thunk<Val>>,
+
+ branch_point: Option<Context>,
}
impl Context {
pub fn new_future() -> Pending<Self> {
@@ -71,14 +73,18 @@
return Ok(val);
}
+ if let Some(branch_point) = &self.0.branch_point {
+ return branch_point.binding(name);
+ }
+
let mut heap = Vec::new();
- self.0.bindings.clone().iter_keys(|k| {
- let conf = strsim::jaro_winkler(&k as &str, &name as &str);
+ for k in self.0.bindings.keys() {
+ let conf = strsim::jaro_winkler(k as &str, &name as &str);
if conf < 0.8 {
- return;
+ continue;
}
- heap.push((conf, k));
- });
+ heap.push((conf, k.clone()));
+ }
heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
bail!(VariableIsNotDefined(
@@ -98,103 +104,91 @@
}
#[must_use]
- pub fn with_var(self, name: impl Into<IStr>, value: Val) -> Self {
- let mut new_bindings = FxHashMap::with_capacity(1);
- new_bindings.insert(name.into(), Thunk::evaluated(value));
- self.extend_bindings(new_bindings)
- }
-
- #[must_use]
- pub fn extend_bindings_sup_this(
- self,
- new_bindings: FxHashMap<IStr, Thunk<Val>>,
- sup_this: SupThis,
- ) -> Self {
- let ctx = &self;
- let dollar = ctx
- .0
- .dollar
- .clone()
- .or_else(|| Some(sup_this.this().clone()));
- let bindings = if new_bindings.is_empty() {
- ctx.0.bindings.clone()
+ pub fn branch_point(self) -> Self {
+ if self.0.bindings.is_empty() {
+ self
} else {
- ctx.0.bindings.clone().extend(new_bindings)
- };
- Self(Cc::new(ContextInternal {
- dollar,
- sup_this: Some(sup_this),
- bindings,
- }))
- }
- #[must_use]
- pub fn extend_bindings(self, new_bindings: FxHashMap<IStr, Thunk<Val>>) -> Self {
- if new_bindings.is_empty() {
- return self;
+ ContextBuilder::extend(self).build()
}
- let ctx = &self;
- let bindings = if new_bindings.is_empty() {
- ctx.0.bindings.clone()
- } else {
- ctx.0.bindings.clone().extend(new_bindings)
- };
- Self(Cc::new(ContextInternal {
- dollar: ctx.0.dollar.clone(),
- sup_this: ctx.0.sup_this.clone(),
- bindings,
- }))
}
}
-#[derive(Default)]
+#[derive(Clone)]
pub struct ContextBuilder {
+ dollar: Option<ObjValue>,
+ sup_this: Option<SupThis>,
bindings: FxHashMap<IStr, Thunk<Val>>,
- extend: Option<Context>,
+ filled: FxHashSet<IStr>,
+ branch_point: Option<Context>,
}
impl ContextBuilder {
pub fn new() -> Self {
- Self::with_capacity(0)
+ Self {
+ dollar: None,
+ sup_this: None,
+ bindings: FxHashMap::new(),
+ filled: FxHashSet::new(),
+ branch_point: None,
+ }
}
- pub fn with_capacity(capacity: usize) -> Self {
+ pub fn extend_fast(parent: Context) -> Self {
Self {
- bindings: FxHashMap::with_capacity(capacity),
- extend: None,
+ dollar: parent.0.dollar.clone(),
+ sup_this: parent.0.sup_this.clone(),
+ bindings: parent.0.bindings.clone(),
+ filled: FxHashSet::new(),
+ branch_point: parent.0.branch_point.clone(),
}
}
pub fn extend(parent: Context) -> Self {
Self {
+ dollar: parent.0.dollar.clone(),
+ sup_this: parent.0.sup_this.clone(),
bindings: FxHashMap::new(),
- extend: Some(parent),
+ filled: FxHashSet::new(),
+ branch_point: Some(parent.clone()),
}
}
- /// # Panics
- ///
- /// If `name` is already bound. Makes no sense to bind same local multiple times,
- /// unless it is separate context layers.
- pub fn bind(&mut self, name: impl Into<IStr>, value: Thunk<Val>) -> &mut Self {
- let old = self.bindings.insert(name.into(), value);
- assert!(old.is_none(), "variable bound twice in single context call");
+ pub fn bind(&mut self, name: impl Into<IStr>, value: Thunk<Val>) {
+ let _ = self.bindings.insert(name.into(), value);
+ }
+ /// After commit, binds would shadow the previous declarations
+ #[must_use]
+ pub fn commit(mut self) -> Self {
+ self.filled.clear();
self
}
- pub fn binds(&mut self, bindings: FxHashMap<IStr, Thunk<Val>>) -> &mut Self {
- for (k, v) in bindings {
- self.bind(k, v);
+ pub fn try_bind(&mut self, name: impl Into<IStr>, value: Thunk<Val>) -> Result<()> {
+ let name = name.into();
+ if !self.filled.insert(name.clone()) {
+ bail!(DuplicateLocalVar(name))
}
- self
+ self.bind(name, value);
+ Ok(())
}
pub fn build(self) -> Context {
- if let Some(parent) = self.extend {
- parent.extend_bindings(self.bindings)
- } else {
- Context(Cc::new(ContextInternal {
- bindings: LayeredHashMap::new(self.bindings),
- dollar: None,
- sup_this: None,
- }))
+ Context(Cc::new(ContextInternal {
+ dollar: self.dollar,
+ sup_this: self.sup_this,
+ bindings: self.bindings,
+ branch_point: self.branch_point,
+ }))
+ }
+ pub fn build_sup_this(mut self, st: SupThis) -> Context {
+ if self.dollar.is_none() {
+ self.dollar = Some(st.this().clone());
}
+ self.sup_this = Some(st);
+ self.build()
+ }
+}
+
+impl Default for ContextBuilder {
+ fn default() -> Self {
+ Self::new()
}
}
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -249,6 +249,10 @@
#[derive(Clone, Trace)]
pub struct Error(Box<(ErrorKind, StackTrace)>);
+
+#[cfg(target_pointer_width = "64")]
+static_assertions::assert_eq_size!(Error, usize);
+
impl Error {
pub fn new(e: ErrorKind) -> Self {
Self(Box::new((e, StackTrace(vec![]))))
crates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -1,30 +1,23 @@
-use std::{collections::HashMap, hash::BuildHasher};
-
-use jrsonnet_interner::IStr;
use jrsonnet_ir::{BindSpec, Destruct};
#[cfg(feature = "exp-preserve-order")]
use crate::evaluate;
use crate::{
- Context, Pending, Thunk, Val, bail,
- error::{ErrorKind::*, Result},
- evaluate_method, evaluate_named_param,
+ Context, ContextBuilder, Pending, Thunk, Val, error::Result, evaluate_method,
+ evaluate_named_param,
};
#[allow(clippy::too_many_lines)]
#[allow(unused_variables)]
-pub fn destruct<H: BuildHasher>(
+pub fn destruct(
d: &Destruct,
parent: Thunk<Val>,
fctx: Pending<Context>,
- new_bindings: &mut HashMap<IStr, Thunk<Val>, H>,
+ new_bindings: &mut ContextBuilder,
) -> Result<()> {
match d {
Destruct::Full(v) => {
- let old = new_bindings.insert(v.clone(), parent);
- if old.is_some() {
- bail!(DuplicateLocalVar(v.clone()))
- }
+ new_bindings.try_bind(v.clone(), parent)?;
}
#[cfg(feature = "exp-destruct")]
Destruct::Skip => {}
@@ -187,10 +180,10 @@
Ok(())
}
-pub fn evaluate_dest<H: BuildHasher>(
+pub fn evaluate_dest(
d: &BindSpec,
fctx: Pending<Context>,
- new_bindings: &mut HashMap<IStr, Thunk<Val>, H>,
+ new_bindings: &mut ContextBuilder,
) -> Result<()> {
match d {
BindSpec::Field { into, value } => {
@@ -210,13 +203,10 @@
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))
- }
+ new_bindings.try_bind(
+ name.clone(),
+ Thunk!(move || Ok(evaluate_method(fctx.unwrap(), name, params, value))),
+ )?;
}
}
Ok(())
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -8,19 +8,17 @@
function::ParamName,
};
use jrsonnet_types::ValType;
-use rustc_hash::FxHashMap;
use self::destructure::destruct;
use crate::{
- Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, ResultExt,
- SupThis, Unbound, Val,
+ Context, ContextBuilder, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,
+ ResultExt, SupThis, Unbound, Val,
arr::ArrValue,
bail,
destructure::evaluate_dest,
error::{ErrorKind::*, suggest_object_fields},
evaluate::operator::{evaluate_binary_op_special, evaluate_unary_op},
function::{CallLocation, FuncDesc, FuncVal, PreparedFuncVal},
- gc::WithCapacityExt as _,
in_frame,
typed::{FromUntyped, IntoUntyped as _, Typed},
val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk},
@@ -130,9 +128,9 @@
guaranteed_reserve = guaranteed_reserve.max(1) * list.len();
for (i, item) in list.iter_lazy().enumerate() {
let fctx = Pending::new();
- let mut new_bindings = FxHashMap::with_capacity(into.binds_len());
- destruct(into, item, fctx.clone(), &mut new_bindings)?;
- let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);
+ let mut ctx = ContextBuilder::extend_fast(ctx.clone());
+ destruct(into, item, fctx.clone(), &mut ctx)?;
+ let ctx = ctx.build().into_future(fctx);
let specs = &specs[1..];
evaluate_comp(
@@ -179,6 +177,7 @@
}
fn evaluate_arr_comp(ctx: Context, expr: &Rc<Expr>, comp_specs: &[CompSpec]) -> Result<ArrValue> {
+ let ctx = ctx.branch_point();
'eager: {
let mut out = Vec::new();
@@ -225,17 +224,13 @@
fn bind(&self, sup_this: SupThis) -> Result<Context> {
let fctx = Context::new_future();
- let mut new_bindings =
- FxHashMap::with_capacity(self.locals.iter().map(BindSpec::binds_len).sum());
+ let ctx = self.fctx.clone();
+ let mut ctx = ContextBuilder::extend(ctx);
for b in self.locals.iter() {
- evaluate_dest(b, fctx.clone(), &mut new_bindings)?;
+ evaluate_dest(b, fctx.clone(), &mut ctx)?;
}
- let ctx = self.fctx.clone();
-
- let ctx = ctx
- .extend_bindings_sup_this(new_bindings, sup_this)
- .into_future(fctx);
+ let ctx = ctx.build_sup_this(sup_this).into_future(fctx);
Ok(ctx)
}
@@ -332,10 +327,7 @@
impl Unbound for DirectUnbound {
type Bound = Context;
fn bind(&self, sup_this: SupThis) -> Result<Context> {
- Ok(self
- .0
- .clone()
- .extend_bindings_sup_this(FxHashMap::new(), sup_this))
+ Ok(ContextBuilder::extend(self.0.clone()).build_sup_this(sup_this))
}
}
@@ -408,12 +400,17 @@
builder.with_super(super_obj);
}
let locals = obj.locals.clone();
- evaluate_comp(ctx, &obj.compspecs, 0, &mut |ctx, reserve| {
- let uctx = evaluate_object_locals(ctx.clone(), locals.clone());
- builder.reserve_fields(reserve);
+ evaluate_comp(
+ ctx.branch_point(),
+ &obj.compspecs,
+ 0,
+ &mut |ctx, reserve| {
+ let uctx = evaluate_object_locals(ctx.clone(), locals.clone());
+ builder.reserve_fields(reserve);
- evaluate_field_member(&mut builder, ctx, uctx, &obj.field)
- })?;
+ evaluate_field_member(&mut builder, ctx, uctx, &obj.field)
+ },
+ )?;
builder.build()
}
@@ -684,13 +681,12 @@
Ok(indexable)
})?,
LocalExpr(bindings, returned) => {
- let mut new_bindings: FxHashMap<IStr, Thunk<Val>> =
- FxHashMap::with_capacity(bindings.iter().map(BindSpec::binds_len).sum());
let fctx = Context::new_future();
+ let mut ctx = ContextBuilder::extend(ctx);
for b in bindings {
- evaluate_dest(b, fctx.clone(), &mut new_bindings)?;
+ evaluate_dest(b, fctx.clone(), &mut ctx)?;
}
- let ctx = ctx.extend_bindings(new_bindings).into_future(fctx);
+ let ctx = ctx.build().into_future(fctx);
evaluate(ctx, returned)?
}
Arr(items) => {
crates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -1,12 +1,10 @@
use jrsonnet_ir::ExprParams;
-use rustc_hash::FxHashMap;
use crate::{
- Context, Thunk,
+ Context, ContextBuilder, Thunk,
destructure::destruct,
error::{ErrorKind::*, Result},
evaluate_named_param,
- gc::WithCapacityExt as _,
};
/// Creates Context, which has all argument default values applied
@@ -14,7 +12,7 @@
pub fn parse_default_function_call(body_ctx: Context, params: &ExprParams) -> Result<Context> {
let fctx = Context::new_future();
- let mut bindings = FxHashMap::with_capacity(params.binds_len());
+ let mut ctx = ContextBuilder::extend(body_ctx);
for param in params.exprs.iter() {
if let Some(v) = ¶m.default {
@@ -27,7 +25,7 @@
Thunk!(move || evaluate_named_param(ctx.unwrap(), &value, name))
},
fctx.clone(),
- &mut bindings,
+ &mut ctx,
)?;
} else {
destruct(
@@ -42,10 +40,10 @@
.into()))
},
fctx.clone(),
- &mut bindings,
+ &mut ctx,
)?;
}
}
- Ok(body_ctx.extend_bindings(bindings).into_future(fctx))
+ Ok(ctx.build().into_future(fctx))
}
crates/jrsonnet-evaluator/src/function/prepared.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/prepared.rs
+++ b/crates/jrsonnet-evaluator/src/function/prepared.rs
@@ -2,12 +2,12 @@
use jrsonnet_gcmodule::{Acyclic, Trace};
use jrsonnet_ir::{ExprParams, IStr, function::FunctionSignature};
-use rustc_hash::{FxHashMap, FxHashSet};
+use rustc_hash::FxHashSet;
use super::{CallLocation, FuncVal};
use crate::{
Context, ContextBuilder, Pending, Result, Thunk, Val, bail, destructure::destruct,
- error::ErrorKind::*, evaluate_named_param, gc::WithCapacityExt,
+ error::ErrorKind::*, evaluate_named_param,
};
#[derive(Debug, Trace, Clone)]
@@ -118,7 +118,7 @@
unnamed: &[Thunk<Val>],
named: &[Thunk<Val>],
) -> Result<Context> {
- let mut passed_args = FxHashMap::with_capacity(params.binds_len());
+ let mut ctx = ContextBuilder::extend(body_ctx);
let destruct_ctx = Pending::new();
@@ -127,7 +127,7 @@
¶ms.exprs[param_idx].destruct,
unnamed.clone(),
destruct_ctx.clone(),
- &mut passed_args,
+ &mut ctx,
)?;
}
@@ -136,18 +136,16 @@
¶ms.exprs[param_idx].destruct,
named[arg_idx].clone(),
destruct_ctx.clone(),
- &mut passed_args,
+ &mut ctx,
)?;
}
if prepared.defaults.is_empty() {
- let body_ctx = body_ctx
- .extend_bindings(passed_args)
- .into_future(destruct_ctx);
+ let body_ctx = ctx.build().into_future(destruct_ctx);
Ok(body_ctx)
} else {
let fctx = Context::new_future();
- let mut defaults = FxHashMap::with_capacity(params.binds_len() - passed_args.len());
+ let mut ctx = ctx.commit();
for param_idx in prepared.defaults.iter().copied() {
// let param = params.0.rc_idx(param_idx);
destruct(
@@ -163,13 +161,10 @@
})
},
fctx.clone(),
- &mut defaults,
+ &mut ctx,
)?;
}
- let mut ctx = ContextBuilder::extend(body_ctx);
- ctx.binds(passed_args);
- ctx.binds(defaults);
Ok(ctx.build().into_future(fctx).into_future(destruct_ctx))
}
}
crates/jrsonnet-evaluator/src/gc.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/gc.rs
+++ b/crates/jrsonnet-evaluator/src/gc.rs
@@ -31,3 +31,5 @@
}
pub fn assert_trace<T: Trace>(_v: &T) {}
+
+pub type ImHashMap<K, V> = im_rc::HashMap<K, V, FxBuildHasher>;
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -15,7 +15,6 @@
mod import;
mod integrations;
pub mod manifest;
-mod map;
mod obj;
pub mod stack;
pub mod stdlib;
@@ -162,19 +161,7 @@
/// During import, this trait will be called to create initial context for file.
/// It may initialize global variables, stdlib for example.
-pub trait ContextInitializer: Trace {
- /// For which size the builder should be preallocated
- fn reserve_vars(&self) -> usize {
- 0
- }
- /// Initialize default file context.
- /// Has default implementation, which calls `populate`.
- /// Prefer to always implement `populate` instead.
- fn initialize(&self, for_file: Source) -> Context {
- let mut builder = ContextBuilder::with_capacity(self.reserve_vars());
- self.populate(for_file, &mut builder);
- builder.build()
- }
+pub trait ContextInitializer {
/// For composability: extend builder. May panic if this initialization is not supported,
/// and the context may only be created via `initialize`.
fn populate(&self, for_file: Source, builder: &mut ContextBuilder);
@@ -182,6 +169,18 @@
/// jrsonnet by itself doesn't use this method, it is allowed for it to panic.
fn as_any(&self) -> &dyn Any;
}
+impl<T> ContextInitializer for &T
+where
+ T: ContextInitializer,
+{
+ fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {
+ (*self).populate(for_file, builder);
+ }
+
+ fn as_any(&self) -> &dyn Any {
+ (*self).as_any()
+ }
+}
/// Context initializer which adds nothing.
impl ContextInitializer for () {
@@ -193,16 +192,8 @@
impl<T> ContextInitializer for Option<T>
where
- T: ContextInitializer,
+ T: ContextInitializer + 'static,
{
- fn initialize(&self, for_file: Source) -> Context {
- if let Some(ctx) = self {
- ctx.initialize(for_file)
- } else {
- ().initialize(for_file)
- }
- }
-
fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {
if let Some(ctx) = self {
ctx.populate(for_file, builder);
@@ -218,12 +209,6 @@
($($gen:ident)*) => {
#[allow(non_snake_case)]
impl<$($gen: ContextInitializer + Trace,)*> ContextInitializer for ($($gen,)*) {
- fn reserve_vars(&self) -> usize {
- let mut out = 0;
- let ($($gen,)*) = self;
- $(out += $gen.reserve_vars();)*
- out
- }
fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {
let ($($gen,)*) = self;
$($gen.populate(for_file.clone(), builder);)*
@@ -453,19 +438,17 @@
/// Creates context with all passed global variables
pub fn create_default_context(&self, source: Source) -> Context {
- self.context_initializer().initialize(source)
+ self.create_default_context_with(source, &())
}
/// Creates context with all passed global variables, calling custom modifier
pub fn create_default_context_with(
&self,
source: Source,
- context_initializer: impl ContextInitializer,
+ context_initializer: &dyn ContextInitializer,
) -> Context {
let default_initializer = self.context_initializer();
- let mut builder = ContextBuilder::with_capacity(
- default_initializer.reserve_vars() + context_initializer.reserve_vars(),
- );
+ let mut builder = ContextBuilder::new();
default_initializer.populate(source.clone(), &mut builder);
context_initializer.populate(source, &mut builder);
@@ -516,20 +499,14 @@
impl State {
/// Parses and evaluates the given snippet
pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {
- let code = code.into();
- let source = Source::new_virtual(name.into(), code.clone());
- let parsed = parse_jsonnet(&code, source.clone()).map_err(|e| ImportSyntaxError {
- path: source.clone(),
- error: Box::new(e),
- })?;
- evaluate(self.create_default_context(source), &parsed)
+ self.evaluate_snippet_with(name, code, &())
}
/// Parses and evaluates the given snippet with custom context modifier
pub fn evaluate_snippet_with(
&self,
name: impl Into<IStr>,
code: impl Into<IStr>,
- context_initializer: impl ContextInitializer,
+ context_initializer: &dyn ContextInitializer,
) -> Result<Val> {
let code = code.into();
let source = Source::new_virtual(name.into(), code.clone());
@@ -587,7 +564,7 @@
}
pub fn context_initializer(
&mut self,
- context_initializer: impl ContextInitializer,
+ context_initializer: impl ContextInitializer + Trace,
) -> &mut Self {
let _ = self
.context_initializer
crates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/manifest.rs
@@ -1,4 +1,4 @@
-use std::{borrow::Cow, fmt::Write, ptr};
+use std::{borrow::Cow, fmt::Write, hint::black_box, ptr};
use crate::{Result, ResultExt, Val, bail, in_description_frame};
@@ -44,6 +44,45 @@
}
}
+pub struct BlackBoxFormat;
+impl ManifestFormat for BlackBoxFormat {
+ #[allow(clippy::only_used_in_recursion)]
+ fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {
+ match val {
+ Val::Bool(v) => {
+ black_box(v);
+ }
+ val @ Val::Null => {
+ black_box(val);
+ }
+ Val::Str(str_value) => {
+ black_box(format!("{str_value}"));
+ }
+ Val::Num(num_value) => {
+ black_box(num_value);
+ }
+ Val::Arr(arr_value) => {
+ for ele in arr_value.iter() {
+ let ele = ele?;
+ self.manifest_buf(ele, buf)?;
+ }
+ }
+ Val::Obj(obj_value) => {
+ for (name, value) in obj_value.iter() {
+ black_box(name);
+ let value = value?;
+ self.manifest_buf(value, buf)?;
+ }
+ }
+ Val::Func(func_val) => {
+ black_box(func_val);
+ bail!("tried to manifest function")
+ }
+ }
+ Ok(())
+ }
+}
+
#[derive(PartialEq, Eq, Clone, Copy)]
enum JsonFormatting {
// Applied in manifestification
@@ -66,7 +105,6 @@
preserve_order: bool,
#[cfg(feature = "exp-bigint")]
preserve_bigints: bool,
- debug_truncate_strings: Option<usize>,
}
impl<'s> JsonFormat<'s> {
@@ -81,7 +119,6 @@
preserve_order,
#[cfg(feature = "exp-bigint")]
preserve_bigints: false,
- debug_truncate_strings: None,
}
}
/// Same format as std.toString, except does not keeps top-level string as-is
@@ -96,7 +133,6 @@
preserve_order: false,
#[cfg(feature = "exp-bigint")]
preserve_bigints: false,
- debug_truncate_strings: None,
}
}
pub fn std_to_json(
@@ -114,7 +150,6 @@
preserve_order,
#[cfg(feature = "exp-bigint")]
preserve_bigints: false,
- debug_truncate_strings: None,
}
}
// Same format as CLI manifestification
@@ -137,7 +172,6 @@
preserve_order,
#[cfg(feature = "exp-bigint")]
preserve_bigints: false,
- debug_truncate_strings: None,
}
}
// Same format as CLI manifestification
@@ -151,7 +185,6 @@
preserve_order: true,
#[cfg(feature = "exp-bigint")]
preserve_bigints: true,
- debug_truncate_strings: Some(256),
}
}
}
@@ -166,7 +199,6 @@
preserve_order: false,
#[cfg(feature = "exp-bigint")]
preserve_bigints: false,
- debug_truncate_strings: None,
}
}
}
@@ -197,18 +229,12 @@
}
Val::Null => buf.push_str("null"),
Val::Str(s) => {
- let flat = s.clone().into_flat();
- if let Some(truncate) = options.debug_truncate_strings {
- if flat.len() > truncate {
- let (start, end) = flat.split_at(truncate / 2);
- let (_, end) = end.split_at(end.len() - truncate / 2);
- escape_string_json_buf(&format!("{start}..{end}"), buf);
- } else {
- escape_string_json_buf(&flat, buf);
- }
- } else {
- escape_string_json_buf(&flat, buf);
- }
+ buf.reserve(2 + s.len());
+ buf.push('"');
+ s.chunks(&mut |c| {
+ escape_string_json_buf_raw(c, buf);
+ });
+ buf.push('"');
}
Val::Num(n) => write!(buf, "{n}").unwrap(),
#[cfg(feature = "exp-bigint")]
@@ -476,15 +502,15 @@
];
pub fn escape_string_json_buf(value: &str, buf: &mut String) {
+ buf.reserve_exact(value.len() + 2);
+ escape_string_json_buf_raw(value, buf);
+}
+
+fn escape_string_json_buf_raw(value: &str, buf: &mut String) {
// Safety: we only write correct utf-8 in this function
let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };
let bytes = value.as_bytes();
-
- // Perfect for ascii strings, removes any reallocations
- buf.reserve(value.len() + 2);
- buf.push(b'"');
-
let mut start = 0;
for (i, &byte) in bytes.iter().enumerate() {
@@ -519,10 +545,8 @@
}
if start == bytes.len() {
- buf.push(b'"');
return;
}
buf.extend_from_slice(&bytes[start..]);
- buf.push(b'"');
}
crates/jrsonnet-evaluator/src/map.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/map.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-use jrsonnet_gcmodule::{Cc, Trace};
-use jrsonnet_interner::IStr;
-use rustc_hash::FxHashMap;
-
-use crate::{Thunk, Val, gc::WithCapacityExt as _};
-
-#[derive(Trace, Debug)]
-#[trace(tracking(force))]
-pub struct LayeredHashMapInternals {
- parent: Option<LayeredHashMap>,
- current: FxHashMap<IStr, Thunk<Val>>,
-}
-
-#[derive(Trace, Debug)]
-pub struct LayeredHashMap(Cc<LayeredHashMapInternals>);
-
-impl LayeredHashMap {
- pub fn iter_keys(self, mut handler: impl FnMut(IStr)) {
- for k in self.0.current.keys() {
- handler(k.clone());
- }
- if let Some(parent) = self.0.parent.clone() {
- parent.iter_keys(handler);
- }
- }
-
- pub(crate) fn new(layer: FxHashMap<IStr, Thunk<Val>>) -> Self {
- Self(Cc::new(LayeredHashMapInternals {
- parent: None,
- current: layer,
- }))
- }
-
- pub fn extend(self, new_layer: FxHashMap<IStr, Thunk<Val>>) -> Self {
- Self(Cc::new(LayeredHashMapInternals {
- parent: Some(self),
- current: new_layer,
- }))
- }
-
- pub fn get(&self, key: &IStr) -> Option<&Thunk<Val>> {
- (self.0)
- .current
- .get(key)
- .or_else(|| self.0.parent.as_ref().and_then(|p| p.get(key)))
- }
-
- pub fn contains_key(&self, key: &IStr) -> bool {
- (self.0).current.contains_key(key)
- || self.0.parent.as_ref().is_some_and(|p| p.contains_key(key))
- }
-}
-
-impl Clone for LayeredHashMap {
- fn clone(&self) -> Self {
- Self(self.0.clone())
- }
-}
-
-impl Default for LayeredHashMap {
- fn default() -> Self {
- Self(Cc::new(LayeredHashMapInternals {
- parent: None,
- current: FxHashMap::new(),
- }))
- }
-}
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use std::{2 cell::RefCell,3 cmp::Ordering,4 fmt::{self, Debug, Display},5 marker::PhantomData,6 mem::replace,7 num::NonZeroU32,8 ops::Deref,9 rc::Rc,10};1112use jrsonnet_gcmodule::{Acyclic, Cc, Trace, cc_dyn};13use jrsonnet_interner::IStr;14pub use jrsonnet_macros::Thunk;15use jrsonnet_types::ValType;16use rustc_hash::FxHashMap;17use thiserror::Error;1819pub use crate::arr::{ArrValue, ArrayLike};20use crate::{21 ObjValue, Result, SupThis, Unbound, WeakSupThis, bail,22 error::{Error, ErrorKind::*},23 function::FuncVal,24 gc::WithCapacityExt as _,25 manifest::{ManifestFormat, ToStringFormat},26 typed::{BoundedUsize, MAX_SAFE_INTEGER, MIN_SAFE_INTEGER},27};2829pub trait ThunkValue: Trace {30 type Output;31 fn get(&self) -> Result<Self::Output>;32}3334#[derive(Trace)]35enum MemoizedClusureThunkInner<D: Trace, T: Trace> {36 Computed(T),37 Errored(Error),38 Waiting {39 env: D,40 // Carries no data, as it is not a real closure, all the41 // captured environment is stored in `env` field.42 #[trace(skip)]43 closure: fn(D) -> Result<T>,44 },45 Pending,46}47#[derive(Trace)]48pub struct MemoizedClosureThunk<D: Trace, T: Trace>(RefCell<MemoizedClusureThunkInner<D, T>>);49impl<D: Trace, T: Trace> MemoizedClosureThunk<D, T> {50 pub fn new(env: D, closure: fn(D) -> Result<T>) -> Self {51 Self(RefCell::new(MemoizedClusureThunkInner::Waiting {52 env,53 closure,54 }))55 }56}5758impl<D: Trace, T: Trace + Clone> ThunkValue for MemoizedClosureThunk<D, T> {59 type Output = T;6061 fn get(&self) -> Result<Self::Output> {62 match &*self.0.borrow() {63 MemoizedClusureThunkInner::Computed(v) => return Ok(v.clone()),64 MemoizedClusureThunkInner::Errored(e) => return Err(e.clone()),65 MemoizedClusureThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),66 MemoizedClusureThunkInner::Waiting { .. } => (),67 }68 let MemoizedClusureThunkInner::Waiting { env, closure } = replace(69 &mut *self.0.borrow_mut(),70 MemoizedClusureThunkInner::Pending,71 ) else {72 unreachable!();73 };74 let new_value = match closure(env) {75 Ok(v) => v,76 Err(e) => {77 *self.0.borrow_mut() = MemoizedClusureThunkInner::Errored(e.clone());78 return Err(e);79 }80 };81 *self.0.borrow_mut() = MemoizedClusureThunkInner::Computed(new_value.clone());82 Ok(new_value)83 }84}8586cc_dyn!(87 /// Lazily evaluated value88 #[derive(Clone)] Thunk<V: Trace>,89 ThunkValue<Output = V>,90 pub fn new() {...}91);9293impl<T: Trace> Thunk<T> {94 pub fn evaluated(val: T) -> Self95 where96 T: Clone,97 {98 #[derive(Trace)]99 struct EvaluatedThunk<T: Trace>(T);100 impl<T> ThunkValue for EvaluatedThunk<T>101 where102 T: Clone + Trace,103 {104 type Output = T;105106 fn get(&self) -> Result<Self::Output> {107 Ok(self.0.clone())108 }109 }110 Self::new(EvaluatedThunk(val))111 }112 pub fn errored(e: Error) -> Self {113 #[derive(Trace)]114 struct ErroredThunk<T: Trace>(Error, PhantomData<T>);115 impl<T> ThunkValue for ErroredThunk<T>116 where117 T: Trace,118 {119 type Output = T;120121 fn get(&self) -> Result<Self::Output> {122 Err(self.0.clone())123 }124 }125 Self::new(ErroredThunk(e, PhantomData))126 }127 pub fn result(res: Result<T, Error>) -> Self128 where129 T: Clone,130 {131 match res {132 Ok(o) => Self::evaluated(o),133 Err(e) => Self::errored(e),134 }135 }136}137138impl<T> Thunk<T>139where140 T: Trace,141{142 pub fn force(&self) -> Result<()> {143 self.evaluate()?;144 Ok(())145 }146147 /// Evaluate thunk, or return cached value148 ///149 /// # Errors150 ///151 /// - Lazy value evaluation returned error152 /// - This method was called during inner value evaluation153 pub fn evaluate(&self) -> Result<T> {154 self.0.get()155 }156}157158pub trait ThunkMapper<Input>: Trace {159 type Output;160 fn map(self, from: Input) -> Result<Self::Output>;161}162impl<Input> Thunk<Input>163where164 Input: Trace,165{166 pub fn map<M>(self, mapper: M) -> Thunk<M::Output>167 where168 M: ThunkMapper<Input>,169 M::Output: Trace + Clone,170 {171 let inner = self;172 Thunk!(move || {173 let value = inner.evaluate()?;174 let mapped = mapper.map(value)?;175 Ok(mapped)176 })177 }178}179180impl<T: Trace + Clone> From<Result<T>> for Thunk<T> {181 fn from(value: Result<T>) -> Self {182 match value {183 Ok(o) => Self::evaluated(o),184 Err(e) => Self::errored(e),185 }186 }187}188impl<T, V: Trace> From<T> for Thunk<V>189where190 T: ThunkValue<Output = V>,191{192 fn from(value: T) -> Self {193 Self::new(value)194 }195}196197impl<T: Trace + Default + Clone> Default for Thunk<T> {198 fn default() -> Self {199 Self::evaluated(T::default())200 }201}202203#[derive(Trace, Clone)]204pub struct CachedUnbound<I, T>205where206 I: Unbound<Bound = T>,207 T: Trace,208{209 cache: Cc<RefCell<FxHashMap<WeakSupThis, T>>>,210 value: I,211}212impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {213 pub fn new(value: I) -> Self {214 Self {215 cache: Cc::new(RefCell::new(FxHashMap::new())),216 value,217 }218 }219}220impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {221 type Bound = T;222 fn bind(&self, sup_this: SupThis) -> Result<T> {223 let cache_key = sup_this.clone().downgrade();224 {225 if let Some(t) = self.cache.borrow().get(&cache_key) {226 return Ok(t.clone());227 }228 }229 let bound = self.value.bind(sup_this)?;230231 {232 let mut cache = self.cache.borrow_mut();233 cache.insert(cache_key, bound.clone());234 }235236 Ok(bound)237 }238}239240impl<T: Debug + Trace> Debug for Thunk<T> {241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {242 write!(f, "Lazy")243 }244}245impl<T: Trace> PartialEq for Thunk<T> {246 fn eq(&self, other: &Self) -> bool {247 Cc::ptr_eq(&self.0, &other.0)248 }249}250251/// Represents a Jsonnet value, which can be sliced or indexed (string or array).252#[allow(clippy::module_name_repetitions)]253pub enum IndexableVal {254 /// String.255 Str(IStr),256 /// Array.257 Arr(ArrValue),258}259impl IndexableVal {260 pub fn is_empty(&self) -> bool {261 match self {262 Self::Str(s) => s.is_empty(),263 Self::Arr(s) => s.is_empty(),264 }265 }266267 pub fn to_array(self) -> ArrValue {268 match self {269 Self::Str(s) => s.chars().collect(),270 Self::Arr(arr) => arr,271 }272 }273 /// Slice the value.274 ///275 /// # Implementation276 ///277 /// For strings, will create a copy of specified interval.278 ///279 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.280 pub fn slice(281 self,282 index: Option<i32>,283 end: Option<i32>,284 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,285 ) -> Result<Self> {286 match &self {287 Self::Str(s) => {288 let mut computed_len = None;289 let mut get_len = || {290 computed_len.unwrap_or_else(|| {291 let len = s.chars().count();292 let _ = computed_len.insert(len);293 len294 })295 };296 let mut get_idx = |pos: Option<i32>, default| {297 match pos {298 #[expect(clippy::cast_sign_loss, reason = "abs value is used")]299 Some(v) if v < 0 => get_len().saturating_sub((-v as isize) as usize),300 // No need to clamp, as iterator interface is used301 #[expect(clippy::cast_sign_loss, reason = "abs value is used")]302 Some(v) => v as usize,303 None => default,304 }305 };306307 let index = get_idx(index, 0);308 let end = get_idx(end, usize::MAX);309 let step = step.as_deref().copied().unwrap_or(1);310311 if index >= end {312 return Ok(Self::Str("".into()));313 }314315 Ok(Self::Str(316 (s.chars()317 .skip(index)318 .take(end - index)319 .step_by(step)320 .collect::<String>())321 .into(),322 ))323 }324 Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(325 index,326 end,327 #[expect(328 clippy::cast_possible_truncation,329 reason = "overflow will result with skip too large which would be equivalent"330 )]331 step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),332 ))),333 }334 }335}336337#[derive(Debug, Clone, Acyclic)]338pub enum StrValue {339 Flat(IStr),340 Tree(Rc<(StrValue, StrValue, usize)>),341}342impl StrValue {343 pub fn concat(a: Self, b: Self) -> Self {344 // TODO: benchmark for an optimal value, currently just a arbitrary choice345 const STRING_EXTEND_THRESHOLD: usize = 100;346347 if a.is_empty() {348 b349 } else if b.is_empty() {350 a351 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {352 Self::Flat(format!("{a}{b}").into())353 } else {354 let len = a.len() + b.len();355 Self::Tree(Rc::new((a, b, len)))356 }357 }358 pub fn into_flat(&self) -> IStr {359 #[cold]360 fn write_buf(s: &StrValue, out: &mut String) {361 match s {362 StrValue::Flat(f) => out.push_str(f),363 StrValue::Tree(t) => {364 write_buf(&t.0, out);365 write_buf(&t.1, out);366 }367 }368 }369 match self {370 Self::Flat(f) => f.clone(),371 Self::Tree(_) => {372 let mut buf = String::with_capacity(self.len());373 write_buf(self, &mut buf);374 buf.into()375 }376 }377 }378 pub fn len(&self) -> usize {379 match self {380 Self::Flat(v) => v.len(),381 Self::Tree(t) => t.2,382 }383 }384 pub fn is_empty(&self) -> bool {385 match self {386 Self::Flat(v) => v.is_empty(),387 // Can't create non-flat empty string388 Self::Tree(_) => false,389 }390 }391}392impl<T> From<T> for StrValue393where394 IStr: From<T>,395{396 fn from(value: T) -> Self {397 Self::Flat(IStr::from(value))398 }399}400impl Display for StrValue {401 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {402 match self {403 Self::Flat(v) => write!(f, "{v}"),404 Self::Tree(t) => {405 write!(f, "{}", t.0)?;406 write!(f, "{}", t.1)407 }408 }409 }410}411impl PartialEq for StrValue {412 // False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.413 #[allow(clippy::unconditional_recursion)]414 fn eq(&self, other: &Self) -> bool {415 let a = self.clone().into_flat();416 let b = other.clone().into_flat();417 a == b418 }419}420impl Eq for StrValue {}421impl PartialOrd for StrValue {422 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {423 Some(self.cmp(other))424 }425}426impl Ord for StrValue {427 fn cmp(&self, other: &Self) -> Ordering {428 let a = self.clone().into_flat();429 let b = other.clone().into_flat();430 a.cmp(&b)431 }432}433434/// Represents jsonnet number435/// Jsonnet numbers are finite f64, with NaNs disallowed436#[derive(Trace, Clone, Copy)]437#[repr(transparent)]438pub struct NumValue(f64);439impl NumValue {440 /// Creates a [`NumValue`], if value is finite and not NaN441 pub fn new(v: f64) -> Option<Self> {442 if !v.is_finite() {443 return None;444 }445 Some(Self(v))446 }447 #[inline]448 pub const fn get(&self) -> f64 {449 self.0450 }451 pub(crate) fn truncate_for_bitwise(self) -> Result<i64> {452 if self.0 < MIN_SAFE_INTEGER || self.0 > MAX_SAFE_INTEGER {453 bail!("numberic value outside of safe integer range for bitwise operation");454 }455 #[expect(clippy::cast_possible_truncation, reason = "intended")]456 Ok(self.0 as i64)457 }458}459impl PartialEq for NumValue {460 fn eq(&self, other: &Self) -> bool {461 self.0 == other.0462 }463}464impl Eq for NumValue {}465impl Ord for NumValue {466 #[inline]467 fn cmp(&self, other: &Self) -> Ordering {468 // Can't use `total_cmp`: its behavior for `-0` and `0`469 // is not following wanted.470 unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }471 }472}473impl PartialOrd for NumValue {474 #[inline]475 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {476 Some(self.cmp(other))477 }478}479impl Debug for NumValue {480 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {481 Debug::fmt(&self.0, f)482 }483}484impl Display for NumValue {485 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {486 Display::fmt(&self.0, f)487 }488}489impl Deref for NumValue {490 type Target = f64;491492 #[inline]493 fn deref(&self) -> &Self::Target {494 &self.0495 }496}497macro_rules! impl_num {498 ($($ty:ty),+) => {$(499 impl From<$ty> for NumValue {500 #[inline]501 fn from(value: $ty) -> Self {502 Self(value.into())503 }504 }505 )+};506}507impl_num!(i8, u8, i16, u16, i32, u32);508509#[derive(Clone, Copy, Debug, Error, Trace)]510pub enum ConvertNumValueError {511 #[error("overflow")]512 Overflow,513 #[error("underflow")]514 Underflow,515 #[error("non-finite")]516 NonFinite,517}518impl From<ConvertNumValueError> for Error {519 fn from(e: ConvertNumValueError) -> Self {520 Self::new(e.into())521 }522}523524macro_rules! impl_try_num {525 ($($ty:ty),+) => {$(526 impl TryFrom<$ty> for NumValue {527 type Error = ConvertNumValueError;528 #[inline]529 fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {530 #[expect(clippy::cast_precision_loss, reason = "precision loss is explicitly handled")]531 let value = value as f64;532 if value < MIN_SAFE_INTEGER {533 return Err(ConvertNumValueError::Underflow)534 } else if value > MAX_SAFE_INTEGER {535 return Err(ConvertNumValueError::Overflow)536 }537 // Number is finite.538 Ok(Self(value))539 }540 }541 )+};542}543impl_try_num!(usize, isize, i64, u64);544545impl TryFrom<f64> for NumValue {546 type Error = ConvertNumValueError;547548 #[inline]549 fn try_from(value: f64) -> Result<Self, Self::Error> {550 Self::new(value).ok_or(ConvertNumValueError::NonFinite)551 }552}553impl TryFrom<f32> for NumValue {554 type Error = ConvertNumValueError;555556 #[inline]557 fn try_from(value: f32) -> Result<Self, Self::Error> {558 Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)559 }560}561562/// Represents any valid Jsonnet value.563#[derive(Debug, Clone, Trace, Default)]564pub enum Val {565 /// Represents a Jsonnet boolean.566 Bool(bool),567 /// Represents a Jsonnet null value.568 #[default]569 Null,570 /// Represents a Jsonnet string.571 Str(StrValue),572 /// Represents a Jsonnet number.573 /// Should be finite, and not NaN574 /// This restriction isn't enforced by enum, as enum field can't be marked as private575 Num(NumValue),576 /// Experimental bigint577 #[cfg(feature = "exp-bigint")]578 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),579 /// Represents a Jsonnet array.580 Arr(ArrValue),581 /// Represents a Jsonnet object.582 Obj(ObjValue),583 /// Represents a Jsonnet function.584 Func(FuncVal),585}586587#[cfg(target_pointer_width = "64")]588static_assertions::assert_eq_size!(Val, [u8; 24]);589590impl From<IndexableVal> for Val {591 fn from(v: IndexableVal) -> Self {592 match v {593 IndexableVal::Str(s) => Self::string(s),594 IndexableVal::Arr(a) => Self::Arr(a),595 }596 }597}598599impl Val {600 pub const fn as_bool(&self) -> Option<bool> {601 match self {602 Self::Bool(v) => Some(*v),603 _ => None,604 }605 }606 pub const fn as_null(&self) -> Option<()> {607 match self {608 Self::Null => Some(()),609 _ => None,610 }611 }612 pub fn as_str(&self) -> Option<IStr> {613 match self {614 Self::Str(s) => Some(s.clone().into_flat()),615 _ => None,616 }617 }618 pub const fn as_num(&self) -> Option<f64> {619 match self {620 Self::Num(n) => Some(n.get()),621 _ => None,622 }623 }624 #[cfg(feature = "exp-bigint")]625 pub fn as_bigint(&self) -> Option<num_bigint::BigInt> {626 match self {627 Self::BigInt(n) => Some(*n.clone()),628 _ => None,629 }630 }631 pub fn as_arr(&self) -> Option<ArrValue> {632 match self {633 Self::Arr(a) => Some(a.clone()),634 _ => None,635 }636 }637 pub fn as_obj(&self) -> Option<ObjValue> {638 match self {639 Self::Obj(o) => Some(o.clone()),640 _ => None,641 }642 }643 pub fn as_func(&self) -> Option<FuncVal> {644 match self {645 Self::Func(f) => Some(f.clone()),646 _ => None,647 }648 }649650 pub const fn value_type(&self) -> ValType {651 match self {652 Self::Str(..) => ValType::Str,653 Self::Num(..) => ValType::Num,654 #[cfg(feature = "exp-bigint")]655 Self::BigInt(..) => ValType::BigInt,656 Self::Arr(..) => ValType::Arr,657 Self::Obj(..) => ValType::Obj,658 Self::Bool(_) => ValType::Bool,659 Self::Null => ValType::Null,660 Self::Func(..) => ValType::Func,661 }662 }663664 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {665 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {666 manifest.manifest(val.clone())667 }668 manifest_dyn(self, &format)669 }670671 pub fn to_string(&self) -> Result<IStr> {672 Ok(match self {673 Self::Bool(true) => "true".into(),674 Self::Bool(false) => "false".into(),675 Self::Null => "null".into(),676 Self::Str(s) => s.clone().into_flat(),677 _ => self.manifest(ToStringFormat).map(IStr::from)?,678 })679 }680681 pub fn into_indexable(self) -> Result<IndexableVal> {682 Ok(match self {683 Self::Str(s) => IndexableVal::Str(s.into_flat()),684 Self::Arr(arr) => IndexableVal::Arr(arr),685 _ => bail!(ValueIsNotIndexable(self.value_type())),686 })687 }688689 pub fn function(function: impl Into<FuncVal>) -> Self {690 Self::Func(function.into())691 }692 pub fn string(string: impl Into<StrValue>) -> Self {693 Self::Str(string.into())694 }695 pub fn num(num: impl Into<NumValue>) -> Self {696 Self::Num(num.into())697 }698 pub fn try_num<V, E>(num: V) -> Result<Self, E>699 where700 NumValue: TryFrom<V, Error = E>,701 {702 Ok(Self::Num(num.try_into()?))703 }704 pub fn arr(a: impl ArrayLike) -> Self {705 Self::Arr(ArrValue::new(a))706 }707}708709impl From<IStr> for Val {710 fn from(value: IStr) -> Self {711 Self::string(value)712 }713}714impl From<String> for Val {715 fn from(value: String) -> Self {716 Self::string(value)717 }718}719impl From<&str> for Val {720 fn from(value: &str) -> Self {721 Self::string(value)722 }723}724impl From<ObjValue> for Val {725 fn from(value: ObjValue) -> Self {726 Self::Obj(value)727 }728}729730const fn is_function_like(val: &Val) -> bool {731 matches!(val, Val::Func(_))732}733734/// Native implementation of `std.primitiveEquals`735pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {736 Ok(match (val_a, val_b) {737 (Val::Bool(a), Val::Bool(b)) => a == b,738 (Val::Null, Val::Null) => true,739 (Val::Str(a), Val::Str(b)) => a == b,740 (Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,741 #[cfg(feature = "exp-bigint")]742 (Val::BigInt(a), Val::BigInt(b)) => a == b,743 (Val::Arr(_), Val::Arr(_)) => {744 bail!("primitiveEquals operates on primitive types, got array")745 }746 (Val::Obj(_), Val::Obj(_)) => {747 bail!("primitiveEquals operates on primitive types, got object")748 }749 (a, b) if is_function_like(a) && is_function_like(b) => {750 bail!("cannot test equality of functions")751 }752 (_, _) => false,753 })754}755756/// Native implementation of `std.equals`757pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {758 if val_a.value_type() != val_b.value_type() {759 return Ok(false);760 }761 match (val_a, val_b) {762 (Val::Arr(a), Val::Arr(b)) => {763 if ArrValue::ptr_eq(a, b) {764 return Ok(true);765 }766 if a.len() != b.len() {767 return Ok(false);768 }769 for (a, b) in a.iter().zip(b.iter()) {770 if !equals(&a?, &b?)? {771 return Ok(false);772 }773 }774 Ok(true)775 }776 (Val::Obj(a), Val::Obj(b)) => {777 if ObjValue::ptr_eq(a, b) {778 return Ok(true);779 }780 let fields = a.fields(781 #[cfg(feature = "exp-preserve-order")]782 false,783 );784 if fields785 != b.fields(786 #[cfg(feature = "exp-preserve-order")]787 false,788 ) {789 return Ok(false);790 }791 for field in fields {792 if !equals(793 &a.get(field.clone())?.expect("field exists"),794 &b.get(field)?.expect("field exists"),795 )? {796 return Ok(false);797 }798 }799 Ok(true)800 }801 (a, b) => Ok(primitive_equals(a, b)?),802 }803}1use std::{2 cell::RefCell,3 cmp::Ordering,4 fmt::{self, Debug, Display},5 marker::PhantomData,6 mem::replace,7 num::NonZeroU32,8 ops::Deref,9 rc::Rc,10};1112use jrsonnet_gcmodule::{Acyclic, Cc, Trace, cc_dyn};13use jrsonnet_interner::IStr;14pub use jrsonnet_macros::Thunk;15use jrsonnet_types::ValType;16use rustc_hash::FxHashMap;17use thiserror::Error;1819pub use crate::arr::{ArrValue, ArrayLike};20use crate::{21 ObjValue, Result, SupThis, Unbound, WeakSupThis, bail,22 error::{Error, ErrorKind::*},23 function::FuncVal,24 gc::WithCapacityExt as _,25 manifest::{ManifestFormat, ToStringFormat},26 typed::{BoundedUsize, MAX_SAFE_INTEGER, MIN_SAFE_INTEGER},27};2829pub trait ThunkValue: Trace {30 type Output;31 fn get(&self) -> Result<Self::Output>;32}3334#[derive(Trace)]35enum MemoizedClusureThunkInner<D: Trace, T: Trace> {36 Computed(T),37 Errored(Error),38 Waiting {39 env: D,40 // Carries no data, as it is not a real closure, all the41 // captured environment is stored in `env` field.42 #[trace(skip)]43 closure: fn(D) -> Result<T>,44 },45 Pending,46}47#[derive(Trace)]48pub struct MemoizedClosureThunk<D: Trace, T: Trace>(RefCell<MemoizedClusureThunkInner<D, T>>);49impl<D: Trace, T: Trace> MemoizedClosureThunk<D, T> {50 pub fn new(env: D, closure: fn(D) -> Result<T>) -> Self {51 Self(RefCell::new(MemoizedClusureThunkInner::Waiting {52 env,53 closure,54 }))55 }56}5758impl<D: Trace, T: Trace + Clone> ThunkValue for MemoizedClosureThunk<D, T> {59 type Output = T;6061 fn get(&self) -> Result<Self::Output> {62 match &*self.0.borrow() {63 MemoizedClusureThunkInner::Computed(v) => return Ok(v.clone()),64 MemoizedClusureThunkInner::Errored(e) => return Err(e.clone()),65 MemoizedClusureThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),66 MemoizedClusureThunkInner::Waiting { .. } => (),67 }68 let MemoizedClusureThunkInner::Waiting { env, closure } = replace(69 &mut *self.0.borrow_mut(),70 MemoizedClusureThunkInner::Pending,71 ) else {72 unreachable!();73 };74 let new_value = match closure(env) {75 Ok(v) => v,76 Err(e) => {77 *self.0.borrow_mut() = MemoizedClusureThunkInner::Errored(e.clone());78 return Err(e);79 }80 };81 *self.0.borrow_mut() = MemoizedClusureThunkInner::Computed(new_value.clone());82 Ok(new_value)83 }84}8586cc_dyn!(87 /// Lazily evaluated value88 #[derive(Clone)] Thunk<V: Trace>,89 ThunkValue<Output = V>,90 pub fn new() {...}91);9293impl<T: Trace> Thunk<T> {94 pub fn evaluated(val: T) -> Self95 where96 T: Clone,97 {98 #[derive(Trace)]99 struct EvaluatedThunk<T: Trace>(T);100 impl<T> ThunkValue for EvaluatedThunk<T>101 where102 T: Clone + Trace,103 {104 type Output = T;105106 fn get(&self) -> Result<Self::Output> {107 Ok(self.0.clone())108 }109 }110 Self::new(EvaluatedThunk(val))111 }112 pub fn errored(e: Error) -> Self {113 #[derive(Trace)]114 struct ErroredThunk<T: Trace>(Error, PhantomData<T>);115 impl<T> ThunkValue for ErroredThunk<T>116 where117 T: Trace,118 {119 type Output = T;120121 fn get(&self) -> Result<Self::Output> {122 Err(self.0.clone())123 }124 }125 Self::new(ErroredThunk(e, PhantomData))126 }127 pub fn result(res: Result<T, Error>) -> Self128 where129 T: Clone,130 {131 match res {132 Ok(o) => Self::evaluated(o),133 Err(e) => Self::errored(e),134 }135 }136}137138impl<T> Thunk<T>139where140 T: Trace,141{142 pub fn force(&self) -> Result<()> {143 self.evaluate()?;144 Ok(())145 }146147 /// Evaluate thunk, or return cached value148 ///149 /// # Errors150 ///151 /// - Lazy value evaluation returned error152 /// - This method was called during inner value evaluation153 pub fn evaluate(&self) -> Result<T> {154 self.0.get()155 }156}157158pub trait ThunkMapper<Input>: Trace {159 type Output;160 fn map(self, from: Input) -> Result<Self::Output>;161}162impl<Input> Thunk<Input>163where164 Input: Trace,165{166 pub fn map<M>(self, mapper: M) -> Thunk<M::Output>167 where168 M: ThunkMapper<Input>,169 M::Output: Trace + Clone,170 {171 let inner = self;172 Thunk!(move || {173 let value = inner.evaluate()?;174 let mapped = mapper.map(value)?;175 Ok(mapped)176 })177 }178}179180impl<T: Trace + Clone> From<Result<T>> for Thunk<T> {181 fn from(value: Result<T>) -> Self {182 match value {183 Ok(o) => Self::evaluated(o),184 Err(e) => Self::errored(e),185 }186 }187}188impl<T, V: Trace> From<T> for Thunk<V>189where190 T: ThunkValue<Output = V>,191{192 fn from(value: T) -> Self {193 Self::new(value)194 }195}196197impl<T: Trace + Default + Clone> Default for Thunk<T> {198 fn default() -> Self {199 Self::evaluated(T::default())200 }201}202203#[derive(Trace, Clone)]204pub struct CachedUnbound<I, T>205where206 I: Unbound<Bound = T>,207 T: Trace,208{209 cache: Cc<RefCell<FxHashMap<WeakSupThis, T>>>,210 value: I,211}212impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {213 pub fn new(value: I) -> Self {214 Self {215 cache: Cc::new(RefCell::new(FxHashMap::new())),216 value,217 }218 }219}220impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {221 type Bound = T;222 fn bind(&self, sup_this: SupThis) -> Result<T> {223 let cache_key = sup_this.clone().downgrade();224 {225 if let Some(t) = self.cache.borrow().get(&cache_key) {226 return Ok(t.clone());227 }228 }229 let bound = self.value.bind(sup_this)?;230231 {232 let mut cache = self.cache.borrow_mut();233 cache.insert(cache_key, bound.clone());234 }235236 Ok(bound)237 }238}239240impl<T: Debug + Trace> Debug for Thunk<T> {241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {242 write!(f, "Lazy")243 }244}245impl<T: Trace> PartialEq for Thunk<T> {246 fn eq(&self, other: &Self) -> bool {247 Cc::ptr_eq(&self.0, &other.0)248 }249}250251/// Represents a Jsonnet value, which can be sliced or indexed (string or array).252#[allow(clippy::module_name_repetitions)]253pub enum IndexableVal {254 /// String.255 Str(IStr),256 /// Array.257 Arr(ArrValue),258}259impl IndexableVal {260 pub fn is_empty(&self) -> bool {261 match self {262 Self::Str(s) => s.is_empty(),263 Self::Arr(s) => s.is_empty(),264 }265 }266267 pub fn to_array(self) -> ArrValue {268 match self {269 Self::Str(s) => s.chars().collect(),270 Self::Arr(arr) => arr,271 }272 }273 /// Slice the value.274 ///275 /// # Implementation276 ///277 /// For strings, will create a copy of specified interval.278 ///279 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.280 pub fn slice(281 self,282 index: Option<i32>,283 end: Option<i32>,284 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,285 ) -> Result<Self> {286 match &self {287 Self::Str(s) => {288 let mut computed_len = None;289 let mut get_len = || {290 computed_len.unwrap_or_else(|| {291 let len = s.chars().count();292 let _ = computed_len.insert(len);293 len294 })295 };296 let mut get_idx = |pos: Option<i32>, default| {297 match pos {298 #[expect(clippy::cast_sign_loss, reason = "abs value is used")]299 Some(v) if v < 0 => get_len().saturating_sub((-v as isize) as usize),300 // No need to clamp, as iterator interface is used301 #[expect(clippy::cast_sign_loss, reason = "abs value is used")]302 Some(v) => v as usize,303 None => default,304 }305 };306307 let index = get_idx(index, 0);308 let end = get_idx(end, usize::MAX);309 let step = step.as_deref().copied().unwrap_or(1);310311 if index >= end {312 return Ok(Self::Str("".into()));313 }314315 Ok(Self::Str(316 (s.chars()317 .skip(index)318 .take(end - index)319 .step_by(step)320 .collect::<String>())321 .into(),322 ))323 }324 Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(325 index,326 end,327 #[expect(328 clippy::cast_possible_truncation,329 reason = "overflow will result with skip too large which would be equivalent"330 )]331 step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),332 ))),333 }334 }335}336337#[derive(Debug, Clone, Acyclic)]338pub enum StrValue {339 Flat(IStr),340 Tree(Rc<(StrValue, StrValue, usize)>),341}342impl StrValue {343 pub fn concat(a: Self, b: Self) -> Self {344 // TODO: benchmark for an optimal value, currently just a arbitrary choice345 const STRING_EXTEND_THRESHOLD: usize = 100;346347 if a.is_empty() {348 b349 } else if b.is_empty() {350 a351 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {352 Self::Flat(format!("{a}{b}").into())353 } else {354 let len = a.len() + b.len();355 Self::Tree(Rc::new((a, b, len)))356 }357 }358 pub fn chunks(&self, c: &mut impl FnMut(&IStr)) {359 fn write_buf(s: &StrValue, c: &mut impl FnMut(&IStr)) {360 match s {361 StrValue::Flat(f) => c(f),362 StrValue::Tree(t) => {363 write_buf(&t.0, c);364 write_buf(&t.1, c);365 }366 }367 }368 write_buf(self, c);369 }370 pub fn into_flat(&self) -> IStr {371 fn write_buf(s: &StrValue, out: &mut String) {372 match s {373 StrValue::Flat(f) => out.push_str(f),374 StrValue::Tree(t) => {375 write_buf(&t.0, out);376 write_buf(&t.1, out);377 }378 }379 }380 match self {381 Self::Flat(f) => f.clone(),382 Self::Tree(_) => {383 let mut buf = String::with_capacity(self.len());384 write_buf(self, &mut buf);385 buf.into()386 }387 }388 }389 pub fn len(&self) -> usize {390 match self {391 Self::Flat(v) => v.len(),392 Self::Tree(t) => t.2,393 }394 }395 pub fn is_empty(&self) -> bool {396 match self {397 Self::Flat(v) => v.is_empty(),398 // Can't create non-flat empty string399 Self::Tree(_) => false,400 }401 }402}403impl<T> From<T> for StrValue404where405 IStr: From<T>,406{407 fn from(value: T) -> Self {408 Self::Flat(IStr::from(value))409 }410}411impl Display for StrValue {412 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {413 match self {414 Self::Flat(v) => write!(f, "{v}"),415 Self::Tree(t) => {416 write!(f, "{}", t.0)?;417 write!(f, "{}", t.1)418 }419 }420 }421}422impl PartialEq for StrValue {423 // False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.424 #[allow(clippy::unconditional_recursion)]425 fn eq(&self, other: &Self) -> bool {426 let a = self.clone().into_flat();427 let b = other.clone().into_flat();428 a == b429 }430}431impl Eq for StrValue {}432impl PartialOrd for StrValue {433 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {434 Some(self.cmp(other))435 }436}437impl Ord for StrValue {438 fn cmp(&self, other: &Self) -> Ordering {439 let a = self.clone().into_flat();440 let b = other.clone().into_flat();441 a.cmp(&b)442 }443}444445/// Represents jsonnet number446/// Jsonnet numbers are finite f64, with NaNs disallowed447#[derive(Trace, Clone, Copy)]448#[repr(transparent)]449pub struct NumValue(f64);450impl NumValue {451 /// Creates a [`NumValue`], if value is finite and not NaN452 pub fn new(v: f64) -> Option<Self> {453 if !v.is_finite() {454 return None;455 }456 Some(Self(v))457 }458 #[inline]459 pub const fn get(&self) -> f64 {460 self.0461 }462 pub(crate) fn truncate_for_bitwise(self) -> Result<i64> {463 if self.0 < MIN_SAFE_INTEGER || self.0 > MAX_SAFE_INTEGER {464 bail!("numberic value outside of safe integer range for bitwise operation");465 }466 #[expect(clippy::cast_possible_truncation, reason = "intended")]467 Ok(self.0 as i64)468 }469}470impl PartialEq for NumValue {471 fn eq(&self, other: &Self) -> bool {472 self.0 == other.0473 }474}475impl Eq for NumValue {}476impl Ord for NumValue {477 #[inline]478 fn cmp(&self, other: &Self) -> Ordering {479 // Can't use `total_cmp`: its behavior for `-0` and `0`480 // is not following wanted.481 unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }482 }483}484impl PartialOrd for NumValue {485 #[inline]486 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {487 Some(self.cmp(other))488 }489}490impl Debug for NumValue {491 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {492 Debug::fmt(&self.0, f)493 }494}495impl Display for NumValue {496 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {497 Display::fmt(&self.0, f)498 }499}500impl Deref for NumValue {501 type Target = f64;502503 #[inline]504 fn deref(&self) -> &Self::Target {505 &self.0506 }507}508macro_rules! impl_num {509 ($($ty:ty),+) => {$(510 impl From<$ty> for NumValue {511 #[inline]512 fn from(value: $ty) -> Self {513 Self(value.into())514 }515 }516 )+};517}518impl_num!(i8, u8, i16, u16, i32, u32);519520#[derive(Clone, Copy, Debug, Error, Trace)]521pub enum ConvertNumValueError {522 #[error("overflow")]523 Overflow,524 #[error("underflow")]525 Underflow,526 #[error("non-finite")]527 NonFinite,528}529impl From<ConvertNumValueError> for Error {530 fn from(e: ConvertNumValueError) -> Self {531 Self::new(e.into())532 }533}534535macro_rules! impl_try_num {536 ($($ty:ty),+) => {$(537 impl TryFrom<$ty> for NumValue {538 type Error = ConvertNumValueError;539 #[inline]540 fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {541 #[expect(clippy::cast_precision_loss, reason = "precision loss is explicitly handled")]542 let value = value as f64;543 if value < MIN_SAFE_INTEGER {544 return Err(ConvertNumValueError::Underflow)545 } else if value > MAX_SAFE_INTEGER {546 return Err(ConvertNumValueError::Overflow)547 }548 // Number is finite.549 Ok(Self(value))550 }551 }552 )+};553}554impl_try_num!(usize, isize, i64, u64);555556impl TryFrom<f64> for NumValue {557 type Error = ConvertNumValueError;558559 #[inline]560 fn try_from(value: f64) -> Result<Self, Self::Error> {561 Self::new(value).ok_or(ConvertNumValueError::NonFinite)562 }563}564impl TryFrom<f32> for NumValue {565 type Error = ConvertNumValueError;566567 #[inline]568 fn try_from(value: f32) -> Result<Self, Self::Error> {569 Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)570 }571}572573/// Represents any valid Jsonnet value.574#[derive(Debug, Clone, Trace, Default)]575pub enum Val {576 /// Represents a Jsonnet boolean.577 Bool(bool),578 /// Represents a Jsonnet null value.579 #[default]580 Null,581 /// Represents a Jsonnet string.582 Str(StrValue),583 /// Represents a Jsonnet number.584 /// Should be finite, and not NaN585 /// This restriction isn't enforced by enum, as enum field can't be marked as private586 Num(NumValue),587 /// Experimental bigint588 #[cfg(feature = "exp-bigint")]589 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),590 /// Represents a Jsonnet array.591 Arr(ArrValue),592 /// Represents a Jsonnet object.593 Obj(ObjValue),594 /// Represents a Jsonnet function.595 Func(FuncVal),596}597598#[cfg(target_pointer_width = "64")]599static_assertions::assert_eq_size!(Val, [u8; 24]);600601impl From<IndexableVal> for Val {602 fn from(v: IndexableVal) -> Self {603 match v {604 IndexableVal::Str(s) => Self::string(s),605 IndexableVal::Arr(a) => Self::Arr(a),606 }607 }608}609610impl Val {611 pub const fn as_bool(&self) -> Option<bool> {612 match self {613 Self::Bool(v) => Some(*v),614 _ => None,615 }616 }617 pub const fn as_null(&self) -> Option<()> {618 match self {619 Self::Null => Some(()),620 _ => None,621 }622 }623 pub fn as_str(&self) -> Option<IStr> {624 match self {625 Self::Str(s) => Some(s.clone().into_flat()),626 _ => None,627 }628 }629 pub const fn as_num(&self) -> Option<f64> {630 match self {631 Self::Num(n) => Some(n.get()),632 _ => None,633 }634 }635 #[cfg(feature = "exp-bigint")]636 pub fn as_bigint(&self) -> Option<num_bigint::BigInt> {637 match self {638 Self::BigInt(n) => Some(*n.clone()),639 _ => None,640 }641 }642 pub fn as_arr(&self) -> Option<ArrValue> {643 match self {644 Self::Arr(a) => Some(a.clone()),645 _ => None,646 }647 }648 pub fn as_obj(&self) -> Option<ObjValue> {649 match self {650 Self::Obj(o) => Some(o.clone()),651 _ => None,652 }653 }654 pub fn as_func(&self) -> Option<FuncVal> {655 match self {656 Self::Func(f) => Some(f.clone()),657 _ => None,658 }659 }660661 pub const fn value_type(&self) -> ValType {662 match self {663 Self::Str(..) => ValType::Str,664 Self::Num(..) => ValType::Num,665 #[cfg(feature = "exp-bigint")]666 Self::BigInt(..) => ValType::BigInt,667 Self::Arr(..) => ValType::Arr,668 Self::Obj(..) => ValType::Obj,669 Self::Bool(_) => ValType::Bool,670 Self::Null => ValType::Null,671 Self::Func(..) => ValType::Func,672 }673 }674675 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {676 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {677 manifest.manifest(val.clone())678 }679 manifest_dyn(self, &format)680 }681682 pub fn to_string(&self) -> Result<IStr> {683 Ok(match self {684 Self::Bool(true) => "true".into(),685 Self::Bool(false) => "false".into(),686 Self::Null => "null".into(),687 Self::Str(s) => s.clone().into_flat(),688 _ => self.manifest(ToStringFormat).map(IStr::from)?,689 })690 }691692 pub fn into_indexable(self) -> Result<IndexableVal> {693 Ok(match self {694 Self::Str(s) => IndexableVal::Str(s.into_flat()),695 Self::Arr(arr) => IndexableVal::Arr(arr),696 _ => bail!(ValueIsNotIndexable(self.value_type())),697 })698 }699700 pub fn function(function: impl Into<FuncVal>) -> Self {701 Self::Func(function.into())702 }703 pub fn string(string: impl Into<StrValue>) -> Self {704 Self::Str(string.into())705 }706 pub fn num(num: impl Into<NumValue>) -> Self {707 Self::Num(num.into())708 }709 pub fn try_num<V, E>(num: V) -> Result<Self, E>710 where711 NumValue: TryFrom<V, Error = E>,712 {713 Ok(Self::Num(num.try_into()?))714 }715 pub fn arr(a: impl ArrayLike) -> Self {716 Self::Arr(ArrValue::new(a))717 }718}719720impl From<IStr> for Val {721 fn from(value: IStr) -> Self {722 Self::string(value)723 }724}725impl From<String> for Val {726 fn from(value: String) -> Self {727 Self::string(value)728 }729}730impl From<&str> for Val {731 fn from(value: &str) -> Self {732 Self::string(value)733 }734}735impl From<ObjValue> for Val {736 fn from(value: ObjValue) -> Self {737 Self::Obj(value)738 }739}740741const fn is_function_like(val: &Val) -> bool {742 matches!(val, Val::Func(_))743}744745/// Native implementation of `std.primitiveEquals`746pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {747 Ok(match (val_a, val_b) {748 (Val::Bool(a), Val::Bool(b)) => a == b,749 (Val::Null, Val::Null) => true,750 (Val::Str(a), Val::Str(b)) => a == b,751 (Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,752 #[cfg(feature = "exp-bigint")]753 (Val::BigInt(a), Val::BigInt(b)) => a == b,754 (Val::Arr(_), Val::Arr(_)) => {755 bail!("primitiveEquals operates on primitive types, got array")756 }757 (Val::Obj(_), Val::Obj(_)) => {758 bail!("primitiveEquals operates on primitive types, got object")759 }760 (a, b) if is_function_like(a) && is_function_like(b) => {761 bail!("cannot test equality of functions")762 }763 (_, _) => false,764 })765}766767/// Native implementation of `std.equals`768pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {769 if val_a.value_type() != val_b.value_type() {770 return Ok(false);771 }772 match (val_a, val_b) {773 (Val::Arr(a), Val::Arr(b)) => {774 if ArrValue::ptr_eq(a, b) {775 return Ok(true);776 }777 if a.len() != b.len() {778 return Ok(false);779 }780 for (a, b) in a.iter().zip(b.iter()) {781 if !equals(&a?, &b?)? {782 return Ok(false);783 }784 }785 Ok(true)786 }787 (Val::Obj(a), Val::Obj(b)) => {788 if ObjValue::ptr_eq(a, b) {789 return Ok(true);790 }791 let fields = a.fields(792 #[cfg(feature = "exp-preserve-order")]793 false,794 );795 if fields796 != b.fields(797 #[cfg(feature = "exp-preserve-order")]798 false,799 ) {800 return Ok(false);801 }802 for field in fields {803 if !equals(804 &a.get(field.clone())?.expect("field exists"),805 &b.get(field)?.expect("field exists"),806 )? {807 return Ok(false);808 }809 }810 Ok(true)811 }812 (a, b) => Ok(primitive_equals(a, b)?),813 }814}crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -545,9 +545,6 @@
}
}
impl jrsonnet_evaluator::ContextInitializer for ContextInitializer {
- fn reserve_vars(&self) -> usize {
- 1
- }
fn populate(&self, source: Source, builder: &mut ContextBuilder) {
let mut std = ObjValueBuilder::new();
std.with_super(self.stdlib_obj.clone());
tests/Cargo.tomldiffbeforeafterboth--- a/tests/Cargo.toml
+++ b/tests/Cargo.toml
@@ -18,6 +18,7 @@
jrsonnet-evaluator.workspace = true
jrsonnet-gcmodule.workspace = true
jrsonnet-stdlib.workspace = true
+mimallocator.workspace = true
serde.workspace = true
serde_json.workspace = true