difftreelog
refactor dyn ObjValue
in: master
5 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -219,6 +219,17 @@
]
[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -346,6 +357,7 @@
"anyhow",
"async-trait",
"bincode",
+ "derivative",
"hashbrown 0.13.2",
"jrsonnet-gcmodule",
"jrsonnet-interner",
crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -58,3 +58,4 @@
async-trait = { version = "0.1.60", optional = true }
# Bigint
num-bigint = { version = "0.4.3", features = ["serde"], optional = true }
+derivative = "2.2.0"
crates/jrsonnet-evaluator/src/gc.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/gc.rs
+++ b/crates/jrsonnet-evaluator/src/gc.rs
@@ -116,7 +116,10 @@
}
}
-pub struct GcHashMap<K, V>(pub HashMap<K, V, BuildHasherDefault<FxHasher>>);
+#[derive(Debug)]
+pub struct GcHashMap<K, V>(
+ pub HashMap<K, V, BuildHasherDefault<FxHasher>>
+);
impl<K, V> GcHashMap<K, V> {
pub fn new() -> Self {
Self(HashMap::default())
crates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -1,4 +1,5 @@
use std::{
+ any::Any,
cell::RefCell,
fmt::Debug,
hash::{Hash, Hasher},
@@ -94,11 +95,49 @@
use ordering::*;
+// 0 - add
+// 12 - visibility
+#[derive(Clone, Copy)]
+pub struct ObjFieldFlags(u8);
+impl ObjFieldFlags {
+ fn new(add: bool, visibility: Visibility) -> Self {
+ let mut v = 0;
+ if add {
+ v |= 1;
+ }
+ v |= match visibility {
+ Visibility::Normal => 0b000,
+ Visibility::Hidden => 0b010,
+ Visibility::Unhide => 0b100,
+ };
+ Self(v)
+ }
+ pub fn add(&self) -> bool {
+ self.0 & 1 != 0
+ }
+ pub fn visibility(&self) -> Visibility {
+ match (self.0 & 0b110) >> 1 {
+ 0b00 => Visibility::Normal,
+ 0b01 => Visibility::Hidden,
+ 0b10 => Visibility::Unhide,
+ _ => unreachable!(),
+ }
+ }
+}
+impl Debug for ObjFieldFlags {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("ObjFieldFlags")
+ .field("add", &self.add())
+ .field("visibility", &self.visibility())
+ .finish()
+ }
+}
+
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Trace)]
pub struct ObjMember {
- pub add: bool,
- pub visibility: Visibility,
+ #[trace(skip)]
+ flags: ObjFieldFlags,
original_index: FieldIndex,
pub invoke: MaybeUnbound,
pub location: Option<ExprLocation>,
@@ -121,18 +160,54 @@
#[allow(clippy::module_name_repetitions)]
#[derive(Trace)]
#[trace(tracking(force))]
-pub struct ObjValueInternals {
+pub struct OopObject {
sup: Option<ObjValue>,
- this: Option<ObjValue>,
-
+ // this: Option<ObjValue>,
assertions: Cc<Vec<TraceBox<dyn ObjectAssertion>>>,
assertions_ran: RefCell<GcHashSet<ObjValue>>,
this_entries: Cc<GcHashMap<IStr, ObjMember>>,
value_cache: RefCell<GcHashMap<(IStr, Option<WeakObjValue>), CacheValue>>,
}
+impl Debug for OopObject {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("OopObject")
+ .field("sup", &self.sup)
+ // .field("assertions", &self.assertions)
+ // .field("assertions_ran", &self.assertions_ran)
+ .field("this_entries", &self.this_entries)
+ // .field("value_cache", &self.value_cache)
+ .finish()
+ }
+}
+
+type EnumFieldsHandler<'a> = dyn FnMut(SuperDepth, FieldIndex, IStr, Visibility) -> bool + 'a;
+
+pub trait ObjectLike: Trace + Any + Debug {
+ fn extend_from(&self, sup: ObjValue) -> ObjValue;
+ /// When using standalone super in object, `this.super_obj.with_this(this)` is executed
+ fn with_this(&self, me: ObjValue, this: ObjValue) -> ObjValue {
+ ObjValue::new(ThisOverride { inner: me, this })
+ }
+ fn this(&self) -> Option<ObjValue> {
+ None
+ }
+ fn len(&self) -> usize;
+ fn is_empty(&self) -> bool;
+ // If callback returns false, iteration stops
+ fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool;
+
+ fn has_field_include_hidden(&self, name: IStr) -> bool;
+ fn has_field(&self, name: IStr) -> bool;
+
+ fn get_for(&self, key: IStr, this: ObjValue) -> Result<Option<Val>>;
+ fn get_for_uncached(&self, key: IStr, this: ObjValue) -> Result<Option<Val>>;
+ fn field_visibility(&self, field: IStr) -> Option<Visibility>;
+
+ fn run_assertions_raw(&self, this: ObjValue) -> Result<()>;
+}
#[derive(Clone, Trace)]
-pub struct WeakObjValue(#[trace(skip)] pub(crate) Weak<ObjValueInternals>);
+pub struct WeakObjValue(#[trace(skip)] pub(crate) Weak<TraceBox<dyn ObjectLike>>);
impl PartialEq for WeakObjValue {
fn eq(&self, other: &Self) -> bool {
@@ -150,138 +225,259 @@
}
#[allow(clippy::module_name_repetitions)]
-#[derive(Clone, Trace)]
-pub struct ObjValue(pub(crate) Cc<ObjValueInternals>);
-impl Debug for ObjValue {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- if let Some(super_obj) = self.0.sup.as_ref() {
- if f.alternate() {
- write!(f, "{super_obj:#?}")?;
- } else {
- write!(f, "{super_obj:?}")?;
- }
- write!(f, " + ")?;
- }
- let mut debug = f.debug_struct("ObjValue");
- for (name, member) in self.0.this_entries.iter() {
- debug.field(name, member);
- }
- debug.finish_non_exhaustive()
+#[derive(Clone, Trace, Debug)]
+pub struct ObjValue(pub(crate) Cc<TraceBox<dyn ObjectLike>>);
+
+#[derive(Debug, Trace)]
+struct EmptyObject;
+impl ObjectLike for EmptyObject {
+ fn extend_from(&self, sup: ObjValue) -> ObjValue {
+ // obj + {} == obj
+ sup
+ }
+
+ fn this(&self) -> Option<ObjValue> {
+ None
+ }
+
+ fn len(&self) -> usize {
+ 0
}
+
+ fn is_empty(&self) -> bool {
+ true
+ }
+
+ fn enum_fields(&self, _depth: SuperDepth, _handler: &mut EnumFieldsHandler<'_>) -> bool {
+ false
+ }
+
+ fn has_field_include_hidden(&self, _name: IStr) -> bool {
+ false
+ }
+
+ fn has_field(&self, _name: IStr) -> bool {
+ false
+ }
+
+ fn get_for(&self, _key: IStr, _this: ObjValue) -> Result<Option<Val>> {
+ Ok(None)
+ }
+ fn get_for_uncached(&self, _key: IStr, _this: ObjValue) -> Result<Option<Val>> {
+ Ok(None)
+ }
+
+ fn run_assertions_raw(&self, _this: ObjValue) -> Result<()> {
+ Ok(())
+ }
+
+ fn field_visibility(&self, _field: IStr) -> Option<Visibility> {
+ None
+ }
}
+#[derive(Trace, Debug)]
+struct ThisOverride {
+ inner: ObjValue,
+ this: ObjValue,
+}
+impl ObjectLike for ThisOverride {
+ fn with_this(&self, _me: ObjValue, this: ObjValue) -> ObjValue {
+ ObjValue::new(ThisOverride {
+ inner: self.inner.clone(),
+ this,
+ })
+ }
+
+ fn extend_from(&self, sup: ObjValue) -> ObjValue {
+ self.inner.extend_from(sup).with_this(self.this.clone())
+ }
+
+ fn this(&self) -> Option<ObjValue> {
+ Some(self.this.clone())
+ }
+
+ fn len(&self) -> usize {
+ self.inner.len()
+ }
+
+ fn is_empty(&self) -> bool {
+ self.inner.is_empty()
+ }
+
+ fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool {
+ self.inner.enum_fields(depth, handler)
+ }
+
+ fn has_field_include_hidden(&self, name: IStr) -> bool {
+ self.inner.has_field_include_hidden(name)
+ }
+
+ fn has_field(&self, name: IStr) -> bool {
+ self.inner.has_field(name)
+ }
+
+ fn get_for(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {
+ self.inner.get_for(key, this)
+ }
+
+ fn get_for_uncached(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {
+ self.inner.get_raw(key, this)
+ }
+
+ fn field_visibility(&self, field: IStr) -> Option<Visibility> {
+ self.inner.field_visibility(field)
+ }
+
+ fn run_assertions_raw(&self, this: ObjValue) -> Result<()> {
+ self.inner.run_assertions_raw(this)
+ }
+}
+
impl ObjValue {
- pub fn new(
- sup: Option<Self>,
- this_entries: Cc<GcHashMap<IStr, ObjMember>>,
- assertions: Cc<Vec<TraceBox<dyn ObjectAssertion>>>,
- ) -> Self {
- Self(Cc::new(ObjValueInternals {
- sup,
- this: None,
- assertions,
- assertions_ran: RefCell::new(GcHashSet::new()),
- this_entries,
- value_cache: RefCell::new(GcHashMap::new()),
- }))
+ pub fn new(v: impl ObjectLike) -> Self {
+ Self(Cc::new(tb!(v)))
}
pub fn new_empty() -> Self {
- Self::new(None, Cc::new(GcHashMap::new()), Cc::new(Vec::new()))
+ Self::new(EmptyObject)
}
pub fn builder() -> ObjValueBuilder {
ObjValueBuilder::new()
}
pub fn builder_with_capacity(capacity: usize) -> ObjValueBuilder {
ObjValueBuilder::with_capacity(capacity)
- }
- #[must_use]
- pub fn extend_from(&self, sup: Self) -> Self {
- match &self.0.sup {
- None => Self::new(
- Some(sup),
- self.0.this_entries.clone(),
- self.0.assertions.clone(),
- ),
- Some(v) => Self::new(
- Some(v.extend_from(sup)),
- self.0.this_entries.clone(),
- self.0.assertions.clone(),
- ),
- }
}
pub(crate) fn extend_with_raw_member(self, key: IStr, value: ObjMember) -> Self {
- let mut new = GcHashMap::with_capacity(1);
- new.insert(key, value);
- Self::new(Some(self), Cc::new(new), Cc::new(Vec::new()))
+ // let mut new = GcHashMap::with_capacity(1);
+ // new.insert(key, value);
+ // Self::new(Some(self), Cc::new(new), Cc::new(Vec::new()))
+ todo!()
}
pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder<ExtendBuilder<'_>> {
ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default())
}
#[must_use]
+ pub fn extend_from(&self, sup: Self) -> Self {
+ self.0.extend_from(sup)
+ }
+ #[must_use]
pub fn with_this(&self, this: Self) -> Self {
- Self(Cc::new(ObjValueInternals {
- sup: self.0.sup.clone(),
- assertions: self.0.assertions.clone(),
- assertions_ran: RefCell::new(GcHashSet::new()),
- this: Some(this),
- this_entries: self.0.this_entries.clone(),
- value_cache: RefCell::new(GcHashMap::new()),
- }))
+ self.0.with_this(self.clone(), this)
}
-
pub fn len(&self) -> usize {
- self.fields_visibility()
- .into_iter()
- .filter(|(_, (visible, _))| *visible)
- .count()
+ self.0.len()
+ }
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+ pub fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool {
+ self.0.enum_fields(depth, handler)
}
- pub fn is_empty(&self) -> bool {
- if !self.0.this_entries.is_empty() {
- return false;
+ pub fn has_field_include_hidden(&self, name: IStr) -> bool {
+ self.0.has_field_include_hidden(name)
+ }
+ pub fn has_field(&self, name: IStr) -> bool {
+ self.0.has_field(name)
+ }
+ pub fn has_field_ex(&self, name: IStr, include_hidden: bool) -> bool {
+ if include_hidden {
+ self.has_field_include_hidden(name)
+ } else {
+ self.has_field(name)
}
- self.0.sup.as_ref().map_or(true, Self::is_empty)
}
- /// Run callback for every field found in object
- ///
- /// Returns true if ended prematurely
- pub(crate) fn enum_fields(
+ pub fn get(&self, key: IStr) -> Result<Option<Val>> {
+ self.run_assertions()?;
+ self.get_for(key, self.0.this().unwrap_or(self.clone()))
+ }
+
+ pub fn get_for(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {
+ self.0.get_for(key, this)
+ }
+
+ fn get_raw(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {
+ self.0.get_for_uncached(key, this)
+ }
+
+ fn field_visibility(&self, field: IStr) -> Option<Visibility> {
+ self.0.field_visibility(field)
+ }
+
+ pub fn run_assertions(&self) -> Result<()> {
+ // FIXME: Should it use `self.0.this()` in case of standalone super?
+ self.run_assertions_raw(self.clone())
+ }
+ fn run_assertions_raw(&self, this: ObjValue) -> Result<()> {
+ self.0.run_assertions_raw(this)
+ }
+
+ pub fn iter(
&self,
- depth: SuperDepth,
- handler: &mut impl FnMut(SuperDepth, &IStr, &ObjMember) -> bool,
- ) -> bool {
- if let Some(s) = &self.0.sup {
- if s.enum_fields(depth.deeper(), handler) {
- return true;
- }
+ #[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+ ) -> impl Iterator<Item = (IStr, Result<Val>)> + '_ {
+ let fields = self.fields(
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ );
+ fields.into_iter().map(|field| {
+ (
+ field.clone(),
+ self.get(field)
+ .map(|opt| opt.expect("iterating over keys, field exists")),
+ )
+ })
+ }
+ pub fn get_lazy(&self, key: IStr) -> Option<Thunk<Val>> {
+ #[derive(Trace)]
+ struct ThunkGet {
+ obj: ObjValue,
+ key: IStr,
}
- for (name, member) in self.0.this_entries.iter() {
- if handler(depth, name, member) {
- return true;
+ impl ThunkValue for ThunkGet {
+ type Output = Val;
+
+ fn get(self: Box<Self>) -> Result<Self::Output> {
+ Ok(self.obj.get(self.key)?.expect("field exists"))
}
}
- false
+
+ if !self.has_field_ex(key.clone(), true) {
+ return None;
+ }
+ Some(Thunk::new(ThunkGet {
+ obj: self.clone(),
+ key,
+ }))
}
-
- pub fn fields_visibility(&self) -> FxHashMap<IStr, (bool, FieldSortKey)> {
+ pub fn ptr_eq(a: &Self, b: &Self) -> bool {
+ Cc::ptr_eq(&a.0, &b.0)
+ }
+ pub fn downgrade(self) -> WeakObjValue {
+ WeakObjValue(self.0.downgrade())
+ }
+ fn fields_visibility(&self) -> FxHashMap<IStr, (bool, FieldSortKey)> {
let mut out = FxHashMap::default();
- self.enum_fields(SuperDepth::default(), &mut |depth, name, member| {
- let new_sort_key = FieldSortKey::new(depth, member.original_index);
- let entry = out.entry(name.clone());
- let (visible, _) = entry.or_insert((true, new_sort_key));
- match member.visibility {
- Visibility::Normal => {}
- Visibility::Hidden => {
- *visible = false;
- }
- Visibility::Unhide => {
- *visible = true;
- }
- };
- false
- });
+ self.enum_fields(
+ SuperDepth::default(),
+ &mut |depth, index, name, visibility| {
+ let new_sort_key = FieldSortKey::new(depth, index);
+ let entry = out.entry(name.clone());
+ let (visible, _) = entry.or_insert((true, new_sort_key));
+ match visibility {
+ Visibility::Normal => {}
+ Visibility::Hidden => {
+ *visible = false;
+ }
+ Visibility::Unhide => {
+ *visible = true;
+ }
+ };
+ false
+ },
+ );
out
}
pub fn fields_ex(
@@ -333,95 +529,122 @@
preserve_order,
)
}
+}
- pub fn field_visibility(&self, name: IStr) -> Option<Visibility> {
- if let Some(m) = self.0.this_entries.get(&name) {
- Some(match &m.visibility {
- Visibility::Normal => self
- .0
- .sup
- .as_ref()
- .and_then(|super_obj| super_obj.field_visibility(name))
- .unwrap_or(Visibility::Normal),
- v => *v,
- })
- } else if let Some(super_obj) = &self.0.sup {
- super_obj.field_visibility(name)
- } else {
- None
+impl OopObject {
+ pub fn new(
+ sup: Option<ObjValue>,
+ this_entries: Cc<GcHashMap<IStr, ObjMember>>,
+ assertions: Cc<Vec<TraceBox<dyn ObjectAssertion>>>,
+ ) -> Self {
+ Self {
+ sup,
+ // this: None,
+ assertions,
+ assertions_ran: RefCell::new(GcHashSet::new()),
+ this_entries,
+ value_cache: RefCell::new(GcHashMap::new()),
}
}
- fn has_field_include_hidden(&self, name: IStr) -> bool {
- if self.0.this_entries.contains_key(&name) {
- true
- } else if let Some(super_obj) = &self.0.sup {
- super_obj.has_field_include_hidden(name)
- } else {
- false
- }
+ fn evaluate_this(&self, v: &ObjMember, real_this: ObjValue) -> Result<Val> {
+ v.invoke.evaluate(self.sup.clone(), Some(real_this))
}
- pub fn has_field_ex(&self, name: IStr, include_hidden: bool) -> bool {
- if include_hidden {
- self.has_field_include_hidden(name)
- } else {
- self.has_field(name)
- }
+ // FIXME: Duplication between ObjValue and OopObject
+ fn fields_visibility(&self) -> FxHashMap<IStr, (bool, FieldSortKey)> {
+ let mut out = FxHashMap::default();
+ self.enum_fields(
+ SuperDepth::default(),
+ &mut |depth, index, name, visibility| {
+ let new_sort_key = FieldSortKey::new(depth, index);
+ let entry = out.entry(name.clone());
+ let (visible, _) = entry.or_insert((true, new_sort_key));
+ match visibility {
+ Visibility::Normal => {}
+ Visibility::Hidden => {
+ *visible = false;
+ }
+ Visibility::Unhide => {
+ *visible = true;
+ }
+ };
+ false
+ },
+ );
+ out
}
- pub fn has_field(&self, name: IStr) -> bool {
- self.field_visibility(name)
- .map_or(false, |v| v.is_visible())
+}
+
+impl ObjectLike for OopObject {
+ fn extend_from(&self, sup: ObjValue) -> ObjValue {
+ ObjValue::new(match &self.sup {
+ None => Self::new(
+ Some(sup),
+ self.this_entries.clone(),
+ self.assertions.clone(),
+ ),
+ Some(v) => Self::new(
+ Some(v.extend_from(sup)),
+ self.this_entries.clone(),
+ self.assertions.clone(),
+ ),
+ })
}
- pub fn iter(
- &self,
- #[cfg(feature = "exp-preserve-order")] preserve_order: bool,
- ) -> impl Iterator<Item = (IStr, Result<Val>)> + '_ {
- let fields = self.fields(
- #[cfg(feature = "exp-preserve-order")]
- preserve_order,
- );
- fields.into_iter().map(|field| {
- (
- field.clone(),
- self.get(field)
- .map(|opt| opt.expect("iterating over keys, field exists")),
- )
- })
+ fn len(&self) -> usize {
+ self.fields_visibility()
+ .into_iter()
+ .filter(|(_, (visible, _))| *visible)
+ .count()
}
- pub fn get_lazy(&self, key: IStr) -> Option<Thunk<Val>> {
- #[derive(Trace)]
- struct ThunkGet {
- obj: ObjValue,
- key: IStr,
+
+ fn is_empty(&self) -> bool {
+ if !self.this_entries.is_empty() {
+ return false;
}
- impl ThunkValue for ThunkGet {
- type Output = Val;
+ self.sup.as_ref().map_or(true, ObjValue::is_empty)
+ }
- fn get(self: Box<Self>) -> Result<Self::Output> {
- Ok(self.obj.get(self.key)?.expect("field exists"))
+ /// Run callback for every field found in object
+ ///
+ /// Returns true if ended prematurely
+ fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool {
+ if let Some(s) = &self.sup {
+ if s.enum_fields(depth.deeper(), handler) {
+ return true;
+ }
+ }
+ for (name, member) in self.this_entries.iter() {
+ if handler(
+ depth,
+ member.original_index,
+ name.clone(),
+ member.flags.visibility(),
+ ) {
+ return true;
}
}
+ false
+ }
- if !self.has_field_ex(key.clone(), true) {
- return None;
+ fn has_field_include_hidden(&self, name: IStr) -> bool {
+ if self.this_entries.contains_key(&name) {
+ true
+ } else if let Some(super_obj) = &self.sup {
+ super_obj.has_field_include_hidden(name)
+ } else {
+ false
}
- Some(Thunk::new(ThunkGet {
- obj: self.clone(),
- key,
- }))
}
- pub fn get(&self, key: IStr) -> Result<Option<Val>> {
- self.get_for(key, self.0.this.clone().unwrap_or_else(|| self.clone()))
+ fn has_field(&self, name: IStr) -> bool {
+ self.field_visibility(name)
+ .map_or(false, |v| v.is_visible())
}
- pub fn get_for(&self, key: IStr, this: Self) -> Result<Option<Val>> {
- self.run_assertions()?;
- let cache_key = (
- key.clone(),
- (!ObjValue::ptr_eq(&this, self)).then(|| this.clone().downgrade()),
- );
- if let Some(v) = self.0.value_cache.borrow().get(&cache_key) {
+
+ fn get_for(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {
+ let cache_key = (key.clone(), Some(this.clone().downgrade()));
+ if let Some(v) = self.value_cache.borrow().get(&cache_key) {
return Ok(match v {
CacheValue::Cached(v) => Some(v.clone()),
CacheValue::NotFound => None,
@@ -429,18 +652,16 @@
CacheValue::Errored(e) => return Err(e.clone()),
});
}
- self.0
- .value_cache
+ self.value_cache
.borrow_mut()
.insert(cache_key.clone(), CacheValue::Pending);
- let value = self.get_raw(key, this).map_err(|e| {
- self.0
- .value_cache
+ let value = self.get_for_uncached(key, this).map_err(|e| {
+ self.value_cache
.borrow_mut()
.insert(cache_key.clone(), CacheValue::Errored(e.clone()));
e
})?;
- self.0.value_cache.borrow_mut().insert(
+ self.value_cache.borrow_mut().insert(
cache_key,
value
.as_ref()
@@ -448,13 +669,12 @@
);
Ok(value)
}
-
- fn get_raw(&self, key: IStr, real_this: Self) -> Result<Option<Val>> {
- match (self.0.this_entries.get(&key), &self.0.sup) {
+ fn get_for_uncached(&self, key: IStr, real_this: ObjValue) -> Result<Option<Val>> {
+ match (self.this_entries.get(&key), &self.sup) {
(Some(k), None) => Ok(Some(self.evaluate_this(k, real_this)?)),
(Some(k), Some(super_obj)) => {
let our = self.evaluate_this(k, real_this.clone())?;
- if k.add {
+ if k.flags.add() {
super_obj
.get_raw(key, real_this)?
.map_or(Ok(Some(our.clone())), |v| {
@@ -468,40 +688,43 @@
(None, None) => Ok(None),
}
}
- fn evaluate_this(&self, v: &ObjMember, real_this: Self) -> Result<Val> {
- v.invoke.evaluate(self.0.sup.clone(), Some(real_this))
+ fn field_visibility(&self, name: IStr) -> Option<Visibility> {
+ if let Some(m) = self.this_entries.get(&name) {
+ Some(match &m.flags.visibility() {
+ Visibility::Normal => self
+ .sup
+ .as_ref()
+ .and_then(|super_obj| super_obj.field_visibility(name))
+ .unwrap_or(Visibility::Normal),
+ v => *v,
+ })
+ } else if let Some(super_obj) = &self.sup {
+ super_obj.field_visibility(name)
+ } else {
+ None
+ }
}
- fn run_assertions_raw(&self, real_this: &Self) -> Result<()> {
- if self.0.assertions.is_empty() {
- if let Some(super_obj) = &self.0.sup {
+ fn run_assertions_raw(&self, real_this: ObjValue) -> Result<()> {
+ if self.assertions.is_empty() {
+ if let Some(super_obj) = &self.sup {
super_obj.run_assertions_raw(real_this)?;
}
return Ok(());
}
- if self.0.assertions_ran.borrow_mut().insert(real_this.clone()) {
- for assertion in self.0.assertions.iter() {
- if let Err(e) = assertion.run(self.0.sup.clone(), Some(real_this.clone())) {
- self.0.assertions_ran.borrow_mut().remove(real_this);
+ if self.assertions_ran.borrow_mut().insert(real_this.clone()) {
+ for assertion in self.assertions.iter() {
+ if let Err(e) = assertion.run(self.sup.clone(), Some(real_this.clone())) {
+ self.assertions_ran.borrow_mut().remove(&real_this);
return Err(e);
}
}
- if let Some(super_obj) = &self.0.sup {
+ if let Some(super_obj) = &self.sup {
super_obj.run_assertions_raw(real_this)?;
}
}
Ok(())
- }
- pub fn run_assertions(&self) -> Result<()> {
- self.run_assertions_raw(self)
- }
-
- pub fn ptr_eq(a: &Self, b: &Self) -> bool {
- Cc::ptr_eq(&a.0, &b.0)
}
- pub fn downgrade(self) -> WeakObjValue {
- WeakObjValue(self.0.downgrade())
- }
}
impl PartialEq for ObjValue {
@@ -556,7 +779,14 @@
}
pub fn build(self) -> ObjValue {
- ObjValue::new(self.sup, Cc::new(self.map), Cc::new(self.assertions))
+ if self.sup.is_none() && self.map.is_empty() && self.assertions.is_empty() {
+ return ObjValue::new_empty();
+ }
+ ObjValue::new(OopObject::new(
+ self.sup,
+ Cc::new(self.map),
+ Cc::new(self.assertions),
+ ))
}
}
impl Default for ObjValueBuilder {
@@ -612,8 +842,7 @@
self.kind,
self.name,
ObjMember {
- add: self.add,
- visibility: self.visibility,
+ flags: ObjFieldFlags::new(self.add, self.visibility),
original_index: self.original_index,
invoke: binding,
location: self.location,
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use std::{2 cell::RefCell,3 fmt::{self, Debug, Display},4 mem::replace,5 rc::Rc,6};78use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_interner::IStr;10use jrsonnet_types::ValType;1112pub use crate::arr::{ArrValue, ArrayLike};13use crate::{14 error::{Error, ErrorKind::*},15 function::FuncVal,16 gc::{GcHashMap, TraceBox},17 manifest::{ManifestFormat, ToStringFormat},18 tb, throw,19 typed::BoundedUsize,20 ObjValue, Result, Unbound, WeakObjValue,21};2223pub trait ThunkValue: Trace {24 type Output;25 fn get(self: Box<Self>) -> Result<Self::Output>;26}2728#[derive(Trace)]29enum ThunkInner<T: Trace> {30 Computed(T),31 Errored(Error),32 Waiting(TraceBox<dyn ThunkValue<Output = T>>),33 Pending,34}3536/// Lazily evaluated value37#[allow(clippy::module_name_repetitions)]38#[derive(Clone, Trace)]39pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4041impl<T: Trace> Thunk<T> {42 pub fn evaluated(val: T) -> Self {43 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))44 }45 pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {46 Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))47 }48 pub fn errored(e: Error) -> Self {49 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))50 }51}5253impl<T> Thunk<T>54where55 T: Clone + Trace,56{57 pub fn force(&self) -> Result<()> {58 self.evaluate()?;59 Ok(())60 }6162 /// Evaluate thunk, or return cached value63 ///64 /// # Errors65 ///66 /// - Lazy value evaluation returned error67 /// - This method was called during inner value evaluation68 pub fn evaluate(&self) -> Result<T> {69 match &*self.0.borrow() {70 ThunkInner::Computed(v) => return Ok(v.clone()),71 ThunkInner::Errored(e) => return Err(e.clone()),72 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),73 ThunkInner::Waiting(..) => (),74 };75 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)76 else {77 unreachable!();78 };79 let new_value = match value.0.get() {80 Ok(v) => v,81 Err(e) => {82 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());83 return Err(e);84 }85 };86 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());87 Ok(new_value)88 }89}9091pub trait ThunkMapper<Input>: Trace {92 type Output;93 fn map(self, from: Input) -> Result<Self::Output>;94}95impl<Input> Thunk<Input>96where97 Input: Trace + Clone,98{99 pub fn map<M>(self, mapper: M) -> Thunk<M::Output>100 where101 M: ThunkMapper<Input>,102 M::Output: Trace,103 {104 #[derive(Trace)]105 struct Mapped<Input: Trace, Mapper: Trace> {106 inner: Thunk<Input>,107 mapper: Mapper,108 }109 impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>110 where111 Input: Trace + Clone,112 Mapper: ThunkMapper<Input>,113 {114 type Output = Mapper::Output;115116 fn get(self: Box<Self>) -> Result<Self::Output> {117 let value = self.inner.evaluate()?;118 let mapped = self.mapper.map(value)?;119 Ok(mapped)120 }121 }122123 Thunk::new(Mapped::<Input, M> {124 inner: self,125 mapper,126 })127 }128}129130impl<T: Trace> From<Result<T>> for Thunk<T> {131 fn from(value: Result<T>) -> Self {132 match value {133 Ok(o) => Self::evaluated(o),134 Err(e) => Self::errored(e),135 }136 }137}138139impl<T: Trace + Default> Default for Thunk<T> {140 fn default() -> Self {141 Self::evaluated(T::default())142 }143}144145type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);146147#[derive(Trace, Clone)]148pub struct CachedUnbound<I, T>149where150 I: Unbound<Bound = T>,151 T: Trace,152{153 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,154 value: I,155}156impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {157 pub fn new(value: I) -> Self {158 Self {159 cache: Cc::new(RefCell::new(GcHashMap::new())),160 value,161 }162 }163}164impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {165 type Bound = T;166 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {167 let cache_key = (168 sup.as_ref().map(|s| s.clone().downgrade()),169 this.as_ref().map(|t| t.clone().downgrade()),170 );171 {172 if let Some(t) = self.cache.borrow().get(&cache_key) {173 return Ok(t.clone());174 }175 }176 let bound = self.value.bind(sup, this)?;177178 {179 let mut cache = self.cache.borrow_mut();180 cache.insert(cache_key, bound.clone());181 }182183 Ok(bound)184 }185}186187impl<T: Debug + Trace> Debug for Thunk<T> {188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {189 write!(f, "Lazy")190 }191}192impl<T: Trace> PartialEq for Thunk<T> {193 fn eq(&self, other: &Self) -> bool {194 Cc::ptr_eq(&self.0, &other.0)195 }196}197198/// Represents a Jsonnet value, which can be sliced or indexed (string or array).199#[allow(clippy::module_name_repetitions)]200pub enum IndexableVal {201 /// String.202 Str(IStr),203 /// Array.204 Arr(ArrValue),205}206impl IndexableVal {207 pub fn to_array(self) -> ArrValue {208 match self {209 IndexableVal::Str(s) => ArrValue::chars(s.chars()),210 IndexableVal::Arr(arr) => arr,211 }212 }213 /// Slice the value.214 ///215 /// # Implementation216 ///217 /// For strings, will create a copy of specified interval.218 ///219 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.220 pub fn slice(221 self,222 index: Option<BoundedUsize<0, { i32::MAX as usize }>>,223 end: Option<BoundedUsize<0, { i32::MAX as usize }>>,224 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,225 ) -> Result<Self> {226 match &self {227 IndexableVal::Str(s) => {228 let index = index.as_deref().copied().unwrap_or(0);229 let end = end.as_deref().copied().unwrap_or(usize::MAX);230 let step = step.as_deref().copied().unwrap_or(1);231232 if index >= end {233 return Ok(Self::Str("".into()));234 }235236 Ok(Self::Str(237 (s.chars()238 .skip(index)239 .take(end - index)240 .step_by(step)241 .collect::<String>())242 .into(),243 ))244 }245 IndexableVal::Arr(arr) => {246 let index = index.as_deref().copied().unwrap_or(0);247 let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());248 let step = step.as_deref().copied().unwrap_or(1);249250 if index >= end {251 return Ok(Self::Arr(ArrValue::empty()));252 }253254 Ok(Self::Arr(255 arr.clone()256 .slice(Some(index), Some(end), Some(step))257 .expect("arguments checked"),258 ))259 }260 }261 }262}263264#[derive(Debug, Clone, Trace)]265pub enum StrValue {266 Flat(IStr),267 Tree(Rc<(StrValue, StrValue, usize)>),268}269impl StrValue {270 pub fn concat(a: StrValue, b: StrValue) -> Self {271 // TODO: benchmark for an optimal value, currently just a arbitrary choice272 const STRING_EXTEND_THRESHOLD: usize = 100;273274 if a.is_empty() {275 b276 } else if b.is_empty() {277 a278 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {279 Self::Flat(format!("{a}{b}").into())280 } else {281 let len = a.len() + b.len();282 Self::Tree(Rc::new((a, b, len)))283 }284 }285 pub fn into_flat(self) -> IStr {286 #[cold]287 fn write_buf(s: &StrValue, out: &mut String) {288 match s {289 StrValue::Flat(f) => out.push_str(f),290 StrValue::Tree(t) => {291 write_buf(&t.0, out);292 write_buf(&t.1, out);293 }294 }295 }296 match self {297 StrValue::Flat(f) => f,298 StrValue::Tree(_) => {299 let mut buf = String::with_capacity(self.len());300 write_buf(&self, &mut buf);301 buf.into()302 }303 }304 }305 pub fn len(&self) -> usize {306 match self {307 StrValue::Flat(v) => v.len(),308 StrValue::Tree(t) => t.2,309 }310 }311 pub fn is_empty(&self) -> bool {312 match self {313 Self::Flat(v) => v.is_empty(),314 // Can't create non-flat empty string315 Self::Tree(_) => false,316 }317 }318}319impl From<&str> for StrValue {320 fn from(value: &str) -> Self {321 Self::Flat(value.into())322 }323}324impl From<String> for StrValue {325 fn from(value: String) -> Self {326 Self::Flat(value.into())327 }328}329impl From<IStr> for StrValue {330 fn from(value: IStr) -> Self {331 Self::Flat(value)332 }333}334impl Display for StrValue {335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {336 match self {337 StrValue::Flat(v) => write!(f, "{v}"),338 StrValue::Tree(t) => {339 write!(f, "{}", t.0)?;340 write!(f, "{}", t.1)341 }342 }343 }344}345impl PartialEq for StrValue {346 fn eq(&self, other: &Self) -> bool {347 let a = self.clone().into_flat();348 let b = other.clone().into_flat();349 a == b350 }351}352impl Eq for StrValue {}353impl PartialOrd for StrValue {354 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {355 Some(self.cmp(other))356 }357}358impl Ord for StrValue {359 fn cmp(&self, other: &Self) -> std::cmp::Ordering {360 let a = self.clone().into_flat();361 let b = other.clone().into_flat();362 a.cmp(&b)363 }364}365366/// Represents any valid Jsonnet value.367#[derive(Debug, Clone, Trace, Default)]368pub enum Val {369 /// Represents a Jsonnet boolean.370 Bool(bool),371 /// Represents a Jsonnet null value.372 #[default]373 Null,374 /// Represents a Jsonnet string.375 Str(StrValue),376 /// Represents a Jsonnet number.377 /// Should be finite, and not NaN378 /// This restriction isn't enforced by enum, as enum field can't be marked as private379 Num(f64),380 /// Experimental bigint381 #[cfg(feature = "exp-bigint")]382 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),383 /// Represents a Jsonnet array.384 Arr(ArrValue),385 /// Represents a Jsonnet object.386 Obj(ObjValue),387 /// Represents a Jsonnet function.388 Func(FuncVal),389}390391#[cfg(target_pointer_width = "64")]392static_assertions::assert_eq_size!(Val, [u8; 24]);393394impl From<IndexableVal> for Val {395 fn from(v: IndexableVal) -> Self {396 match v {397 IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),398 IndexableVal::Arr(a) => Self::Arr(a),399 }400 }401}402403impl Val {404 pub const fn as_bool(&self) -> Option<bool> {405 match self {406 Self::Bool(v) => Some(*v),407 _ => None,408 }409 }410 pub const fn as_null(&self) -> Option<()> {411 match self {412 Self::Null => Some(()),413 _ => None,414 }415 }416 pub fn as_str(&self) -> Option<IStr> {417 match self {418 Self::Str(s) => Some(s.clone().into_flat()),419 _ => None,420 }421 }422 pub const fn as_num(&self) -> Option<f64> {423 match self {424 Self::Num(n) => Some(*n),425 _ => None,426 }427 }428 pub fn as_arr(&self) -> Option<ArrValue> {429 match self {430 Self::Arr(a) => Some(a.clone()),431 _ => None,432 }433 }434 pub fn as_obj(&self) -> Option<ObjValue> {435 match self {436 Self::Obj(o) => Some(o.clone()),437 _ => None,438 }439 }440 pub fn as_func(&self) -> Option<FuncVal> {441 match self {442 Self::Func(f) => Some(f.clone()),443 _ => None,444 }445 }446447 /// Creates `Val::Num` after checking for numeric overflow.448 /// As numbers are `f64`, we can just check for their finity.449 pub fn new_checked_num(num: f64) -> Result<Self> {450 if num.is_finite() {451 Ok(Self::Num(num))452 } else {453 throw!("overflow")454 }455 }456457 pub const fn value_type(&self) -> ValType {458 match self {459 Self::Str(..) => ValType::Str,460 Self::Num(..) => ValType::Num,461 #[cfg(feature = "exp-bigint")]462 Self::BigInt(..) => ValType::BigInt,463 Self::Arr(..) => ValType::Arr,464 Self::Obj(..) => ValType::Obj,465 Self::Bool(_) => ValType::Bool,466 Self::Null => ValType::Null,467 Self::Func(..) => ValType::Func,468 }469 }470471 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {472 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {473 manifest.manifest(val.clone())474 }475 manifest_dyn(self, &format)476 }477478 pub fn to_string(&self) -> Result<IStr> {479 Ok(match self {480 Self::Bool(true) => "true".into(),481 Self::Bool(false) => "false".into(),482 Self::Null => "null".into(),483 Self::Str(s) => s.clone().into_flat(),484 _ => self.manifest(ToStringFormat).map(IStr::from)?,485 })486 }487488 pub fn into_indexable(self) -> Result<IndexableVal> {489 Ok(match self {490 Val::Str(s) => IndexableVal::Str(s.into_flat()),491 Val::Arr(arr) => IndexableVal::Arr(arr),492 _ => throw!(ValueIsNotIndexable(self.value_type())),493 })494 }495}496497const fn is_function_like(val: &Val) -> bool {498 matches!(val, Val::Func(_))499}500501/// Native implementation of `std.primitiveEquals`502pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {503 Ok(match (val_a, val_b) {504 (Val::Bool(a), Val::Bool(b)) => a == b,505 (Val::Null, Val::Null) => true,506 (Val::Str(a), Val::Str(b)) => a == b,507 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,508 #[cfg(feature = "exp-bigint")]509 (Val::BigInt(a), Val::BigInt(b)) => a == b,510 (Val::Arr(_), Val::Arr(_)) => {511 throw!("primitiveEquals operates on primitive types, got array")512 }513 (Val::Obj(_), Val::Obj(_)) => {514 throw!("primitiveEquals operates on primitive types, got object")515 }516 (a, b) if is_function_like(a) && is_function_like(b) => {517 throw!("cannot test equality of functions")518 }519 (_, _) => false,520 })521}522523/// Native implementation of `std.equals`524pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {525 if val_a.value_type() != val_b.value_type() {526 return Ok(false);527 }528 match (val_a, val_b) {529 (Val::Arr(a), Val::Arr(b)) => {530 if ArrValue::ptr_eq(a, b) {531 return Ok(true);532 }533 if a.len() != b.len() {534 return Ok(false);535 }536 for (a, b) in a.iter().zip(b.iter()) {537 if !equals(&a?, &b?)? {538 return Ok(false);539 }540 }541 Ok(true)542 }543 (Val::Obj(a), Val::Obj(b)) => {544 if ObjValue::ptr_eq(a, b) {545 return Ok(true);546 }547 let fields = a.fields(548 #[cfg(feature = "exp-preserve-order")]549 false,550 );551 if fields552 != b.fields(553 #[cfg(feature = "exp-preserve-order")]554 false,555 ) {556 return Ok(false);557 }558 for field in fields {559 if !equals(560 &a.get(field.clone())?.expect("field exists"),561 &b.get(field)?.expect("field exists"),562 )? {563 return Ok(false);564 }565 }566 Ok(true)567 }568 (a, b) => Ok(primitive_equals(a, b)?),569 }570}1use std::{2 cell::RefCell,3 fmt::{self, Debug, Display},4 hash::Hasher,5 mem::replace,6 rc::Rc,7};89use jrsonnet_gcmodule::{Cc, Trace};10use jrsonnet_interner::IStr;11use jrsonnet_types::ValType;12use rustc_hash::FxHasher;1314pub use crate::arr::{ArrValue, ArrayLike};15use crate::{16 error::{Error, ErrorKind::*},17 function::FuncVal,18 gc::{GcHashMap, TraceBox},19 manifest::{ManifestFormat, ToStringFormat},20 tb, throw,21 typed::BoundedUsize,22 ObjValue, Result, Unbound, WeakObjValue,23};2425pub trait ThunkValue: Trace {26 type Output;27 fn get(self: Box<Self>) -> Result<Self::Output>;28}2930#[derive(Trace)]31enum ThunkInner<T: Trace> {32 Computed(T),33 Errored(Error),34 Waiting(TraceBox<dyn ThunkValue<Output = T>>),35 Pending,36}3738/// Lazily evaluated value39#[allow(clippy::module_name_repetitions)]40#[derive(Clone, Trace)]41pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4243impl<T: Trace> Thunk<T> {44 pub fn evaluated(val: T) -> Self {45 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))46 }47 pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {48 Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))49 }50 pub fn errored(e: Error) -> Self {51 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))52 }53}5455impl<T> Thunk<T>56where57 T: Clone + Trace,58{59 pub fn force(&self) -> Result<()> {60 self.evaluate()?;61 Ok(())62 }6364 /// Evaluate thunk, or return cached value65 ///66 /// # Errors67 ///68 /// - Lazy value evaluation returned error69 /// - This method was called during inner value evaluation70 pub fn evaluate(&self) -> Result<T> {71 match &*self.0.borrow() {72 ThunkInner::Computed(v) => return Ok(v.clone()),73 ThunkInner::Errored(e) => return Err(e.clone()),74 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),75 ThunkInner::Waiting(..) => (),76 };77 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)78 else {79 unreachable!();80 };81 let new_value = match value.0.get() {82 Ok(v) => v,83 Err(e) => {84 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());85 return Err(e);86 }87 };88 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());89 Ok(new_value)90 }91}9293pub trait ThunkMapper<Input>: Trace {94 type Output;95 fn map(self, from: Input) -> Result<Self::Output>;96}97impl<Input> Thunk<Input>98where99 Input: Trace + Clone,100{101 pub fn map<M>(self, mapper: M) -> Thunk<M::Output>102 where103 M: ThunkMapper<Input>,104 M::Output: Trace,105 {106 #[derive(Trace)]107 struct Mapped<Input: Trace, Mapper: Trace> {108 inner: Thunk<Input>,109 mapper: Mapper,110 }111 impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>112 where113 Input: Trace + Clone,114 Mapper: ThunkMapper<Input>,115 {116 type Output = Mapper::Output;117118 fn get(self: Box<Self>) -> Result<Self::Output> {119 let value = self.inner.evaluate()?;120 let mapped = self.mapper.map(value)?;121 Ok(mapped)122 }123 }124125 Thunk::new(Mapped::<Input, M> {126 inner: self,127 mapper,128 })129 }130}131132impl<T: Trace> From<Result<T>> for Thunk<T> {133 fn from(value: Result<T>) -> Self {134 match value {135 Ok(o) => Self::evaluated(o),136 Err(e) => Self::errored(e),137 }138 }139}140141impl<T: Trace + Default> Default for Thunk<T> {142 fn default() -> Self {143 Self::evaluated(T::default())144 }145}146147type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);148149#[derive(Trace, Clone)]150pub struct CachedUnbound<I, T>151where152 I: Unbound<Bound = T>,153 T: Trace,154{155 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,156 value: I,157}158impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {159 pub fn new(value: I) -> Self {160 Self {161 cache: Cc::new(RefCell::new(GcHashMap::new())),162 value,163 }164 }165}166impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {167 type Bound = T;168 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {169 let cache_key = (170 sup.as_ref().map(|s| s.clone().downgrade()),171 this.as_ref().map(|t| t.clone().downgrade()),172 );173 {174 if let Some(t) = self.cache.borrow().get(&cache_key) {175 return Ok(t.clone());176 }177 }178 let bound = self.value.bind(sup, this)?;179180 {181 let mut cache = self.cache.borrow_mut();182 cache.insert(cache_key, bound.clone());183 }184185 Ok(bound)186 }187}188189impl<T: Debug + Trace> Debug for Thunk<T> {190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {191 write!(f, "Lazy")192 }193}194impl<T: Trace> PartialEq for Thunk<T> {195 fn eq(&self, other: &Self) -> bool {196 Cc::ptr_eq(&self.0, &other.0)197 }198}199200/// Represents a Jsonnet value, which can be sliced or indexed (string or array).201#[allow(clippy::module_name_repetitions)]202pub enum IndexableVal {203 /// String.204 Str(IStr),205 /// Array.206 Arr(ArrValue),207}208impl IndexableVal {209 pub fn to_array(self) -> ArrValue {210 match self {211 IndexableVal::Str(s) => ArrValue::chars(s.chars()),212 IndexableVal::Arr(arr) => arr,213 }214 }215 /// Slice the value.216 ///217 /// # Implementation218 ///219 /// For strings, will create a copy of specified interval.220 ///221 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.222 pub fn slice(223 self,224 index: Option<BoundedUsize<0, { i32::MAX as usize }>>,225 end: Option<BoundedUsize<0, { i32::MAX as usize }>>,226 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,227 ) -> Result<Self> {228 match &self {229 IndexableVal::Str(s) => {230 let index = index.as_deref().copied().unwrap_or(0);231 let end = end.as_deref().copied().unwrap_or(usize::MAX);232 let step = step.as_deref().copied().unwrap_or(1);233234 if index >= end {235 return Ok(Self::Str("".into()));236 }237238 Ok(Self::Str(239 (s.chars()240 .skip(index)241 .take(end - index)242 .step_by(step)243 .collect::<String>())244 .into(),245 ))246 }247 IndexableVal::Arr(arr) => {248 let index = index.as_deref().copied().unwrap_or(0);249 let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());250 let step = step.as_deref().copied().unwrap_or(1);251252 if index >= end {253 return Ok(Self::Arr(ArrValue::empty()));254 }255256 Ok(Self::Arr(257 arr.clone()258 .slice(Some(index), Some(end), Some(step))259 .expect("arguments checked"),260 ))261 }262 }263 }264}265266#[derive(Debug, Clone, Trace)]267pub enum StrValue {268 Flat(IStr),269 Tree(Rc<(StrValue, StrValue, usize)>),270}271impl StrValue {272 pub fn concat(a: StrValue, b: StrValue) -> Self {273 // TODO: benchmark for an optimal value, currently just a arbitrary choice274 const STRING_EXTEND_THRESHOLD: usize = 100;275276 if a.is_empty() {277 b278 } else if b.is_empty() {279 a280 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {281 Self::Flat(format!("{a}{b}").into())282 } else {283 let len = a.len() + b.len();284 Self::Tree(Rc::new((a, b, len)))285 }286 }287 pub fn into_flat(self) -> IStr {288 #[cold]289 fn write_buf(s: &StrValue, out: &mut String) {290 match s {291 StrValue::Flat(f) => out.push_str(f),292 StrValue::Tree(t) => {293 write_buf(&t.0, out);294 write_buf(&t.1, out);295 }296 }297 }298 match self {299 StrValue::Flat(f) => f,300 StrValue::Tree(_) => {301 let mut buf = String::with_capacity(self.len());302 write_buf(&self, &mut buf);303 buf.into()304 }305 }306 }307 pub fn len(&self) -> usize {308 match self {309 StrValue::Flat(v) => v.len(),310 StrValue::Tree(t) => t.2,311 }312 }313 pub fn is_empty(&self) -> bool {314 match self {315 Self::Flat(v) => v.is_empty(),316 // Can't create non-flat empty string317 Self::Tree(_) => false,318 }319 }320}321impl From<&str> for StrValue {322 fn from(value: &str) -> Self {323 Self::Flat(value.into())324 }325}326impl From<String> for StrValue {327 fn from(value: String) -> Self {328 Self::Flat(value.into())329 }330}331impl From<IStr> for StrValue {332 fn from(value: IStr) -> Self {333 Self::Flat(value)334 }335}336impl Display for StrValue {337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {338 match self {339 StrValue::Flat(v) => write!(f, "{v}"),340 StrValue::Tree(t) => {341 write!(f, "{}", t.0)?;342 write!(f, "{}", t.1)343 }344 }345 }346}347impl PartialEq for StrValue {348 fn eq(&self, other: &Self) -> bool {349 let a = self.clone().into_flat();350 let b = other.clone().into_flat();351 a == b352 }353}354impl Eq for StrValue {}355impl PartialOrd for StrValue {356 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {357 Some(self.cmp(other))358 }359}360impl Ord for StrValue {361 fn cmp(&self, other: &Self) -> std::cmp::Ordering {362 let a = self.clone().into_flat();363 let b = other.clone().into_flat();364 a.cmp(&b)365 }366}367368/// Represents any valid Jsonnet value.369#[derive(Debug, Clone, Trace, Default)]370pub enum Val {371 /// Represents a Jsonnet boolean.372 Bool(bool),373 /// Represents a Jsonnet null value.374 #[default]375 Null,376 /// Represents a Jsonnet string.377 Str(StrValue),378 /// Represents a Jsonnet number.379 /// Should be finite, and not NaN380 /// This restriction isn't enforced by enum, as enum field can't be marked as private381 Num(f64),382 /// Experimental bigint383 #[cfg(feature = "exp-bigint")]384 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),385 /// Represents a Jsonnet array.386 Arr(ArrValue),387 /// Represents a Jsonnet object.388 Obj(ObjValue),389 /// Represents a Jsonnet function.390 Func(FuncVal),391}392393#[cfg(target_pointer_width = "64")]394static_assertions::assert_eq_size!(Val, [u8; 24]);395396impl From<IndexableVal> for Val {397 fn from(v: IndexableVal) -> Self {398 match v {399 IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),400 IndexableVal::Arr(a) => Self::Arr(a),401 }402 }403}404405impl Val {406 pub const fn as_bool(&self) -> Option<bool> {407 match self {408 Self::Bool(v) => Some(*v),409 _ => None,410 }411 }412 pub const fn as_null(&self) -> Option<()> {413 match self {414 Self::Null => Some(()),415 _ => None,416 }417 }418 pub fn as_str(&self) -> Option<IStr> {419 match self {420 Self::Str(s) => Some(s.clone().into_flat()),421 _ => None,422 }423 }424 pub const fn as_num(&self) -> Option<f64> {425 match self {426 Self::Num(n) => Some(*n),427 _ => None,428 }429 }430 pub fn as_arr(&self) -> Option<ArrValue> {431 match self {432 Self::Arr(a) => Some(a.clone()),433 _ => None,434 }435 }436 pub fn as_obj(&self) -> Option<ObjValue> {437 match self {438 Self::Obj(o) => Some(o.clone()),439 _ => None,440 }441 }442 pub fn as_func(&self) -> Option<FuncVal> {443 match self {444 Self::Func(f) => Some(f.clone()),445 _ => None,446 }447 }448449 /// Creates `Val::Num` after checking for numeric overflow.450 /// As numbers are `f64`, we can just check for their finity.451 pub fn new_checked_num(num: f64) -> Result<Self> {452 if num.is_finite() {453 Ok(Self::Num(num))454 } else {455 throw!("overflow")456 }457 }458459 pub const fn value_type(&self) -> ValType {460 match self {461 Self::Str(..) => ValType::Str,462 Self::Num(..) => ValType::Num,463 #[cfg(feature = "exp-bigint")]464 Self::BigInt(..) => ValType::BigInt,465 Self::Arr(..) => ValType::Arr,466 Self::Obj(..) => ValType::Obj,467 Self::Bool(_) => ValType::Bool,468 Self::Null => ValType::Null,469 Self::Func(..) => ValType::Func,470 }471 }472473 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {474 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {475 manifest.manifest(val.clone())476 }477 manifest_dyn(self, &format)478 }479480 pub fn to_string(&self) -> Result<IStr> {481 Ok(match self {482 Self::Bool(true) => "true".into(),483 Self::Bool(false) => "false".into(),484 Self::Null => "null".into(),485 Self::Str(s) => s.clone().into_flat(),486 _ => self.manifest(ToStringFormat).map(IStr::from)?,487 })488 }489490 pub fn into_indexable(self) -> Result<IndexableVal> {491 Ok(match self {492 Val::Str(s) => IndexableVal::Str(s.into_flat()),493 Val::Arr(arr) => IndexableVal::Arr(arr),494 _ => throw!(ValueIsNotIndexable(self.value_type())),495 })496 }497}498499const fn is_function_like(val: &Val) -> bool {500 matches!(val, Val::Func(_))501}502503/// Native implementation of `std.primitiveEquals`504pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {505 Ok(match (val_a, val_b) {506 (Val::Bool(a), Val::Bool(b)) => a == b,507 (Val::Null, Val::Null) => true,508 (Val::Str(a), Val::Str(b)) => a == b,509 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,510 #[cfg(feature = "exp-bigint")]511 (Val::BigInt(a), Val::BigInt(b)) => a == b,512 (Val::Arr(_), Val::Arr(_)) => {513 throw!("primitiveEquals operates on primitive types, got array")514 }515 (Val::Obj(_), Val::Obj(_)) => {516 throw!("primitiveEquals operates on primitive types, got object")517 }518 (a, b) if is_function_like(a) && is_function_like(b) => {519 throw!("cannot test equality of functions")520 }521 (_, _) => false,522 })523}524525/// Native implementation of `std.equals`526pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {527 if val_a.value_type() != val_b.value_type() {528 return Ok(false);529 }530 match (val_a, val_b) {531 (Val::Arr(a), Val::Arr(b)) => {532 if ArrValue::ptr_eq(a, b) {533 return Ok(true);534 }535 if a.len() != b.len() {536 return Ok(false);537 }538 for (a, b) in a.iter().zip(b.iter()) {539 if !equals(&a?, &b?)? {540 return Ok(false);541 }542 }543 Ok(true)544 }545 (Val::Obj(a), Val::Obj(b)) => {546 if ObjValue::ptr_eq(a, b) {547 return Ok(true);548 }549 let fields = a.fields(550 #[cfg(feature = "exp-preserve-order")]551 false,552 );553 if fields554 != b.fields(555 #[cfg(feature = "exp-preserve-order")]556 false,557 ) {558 return Ok(false);559 }560 for field in fields {561 if !equals(562 &a.get(field.clone())?.expect("field exists"),563 &b.get(field)?.expect("field exists"),564 )? {565 return Ok(false);566 }567 }568 Ok(true)569 }570 (a, b) => Ok(primitive_equals(a, b)?),571 }572}