difftreelog
refactor extended strings
in: master
16 files changed
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -17,7 +17,7 @@
function::{CallLocation, FuncDesc, FuncVal},
tb, throw,
typed::Typed,
- val::{CachedUnbound, IndexableVal, Thunk, ThunkValue},
+ val::{CachedUnbound, IndexableVal, StrValue, Thunk, ThunkValue},
Context, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, State,
Unbound, Val,
};
@@ -36,7 +36,7 @@
}
}
Some(match &*expr.0 {
- Expr::Str(s) => Val::Str(s.clone()),
+ Expr::Str(s) => Val::Str(StrValue::Flat(s.clone())),
Expr::Num(n) => Val::Num(*n),
Expr::Literal(LiteralType::False) => Val::Bool(false),
Expr::Literal(LiteralType::True) => Val::Bool(true),
@@ -135,7 +135,7 @@
let fctx = Pending::new();
let mut new_bindings = GcHashMap::with_capacity(var.capacity_hint());
let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(Cc::new(vec![
- Thunk::evaluated(Val::Str(field.clone())),
+ Thunk::evaluated(Val::Str(StrValue::Flat(field.clone()))),
Thunk::new(tb!(ObjectFieldThunk {
field: field.clone(),
obj: obj.clone(),
@@ -436,7 +436,7 @@
Literal(LiteralType::False) => Val::Bool(false),
Literal(LiteralType::Null) => Val::Null,
Parened(e) => evaluate(ctx, e)?,
- Str(v) => Val::Str(v.clone()),
+ Str(v) => Val::Str(StrValue::Flat(v.clone())),
Num(v) => Val::new_checked_num(*v)?,
BinaryOp(v1, o, v2) => evaluate_binary_op_special(ctx, v1, *o, v2)?,
UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,
@@ -457,14 +457,14 @@
ctx.super_obj()
.clone()
.expect("no super found")
- .get_for(name, ctx.this().clone().expect("no this found"))?
+ .get_for(name.into_flat(), ctx.this().clone().expect("no this found"))?
.expect("value not found")
}
Index(value, index) => match (evaluate(ctx.clone(), value)?, evaluate(ctx, index)?) {
(Val::Obj(v), Val::Str(key)) => State::push(
CallLocation::new(loc),
|| format!("field <{key}> access"),
- || match v.get(key.clone()) {
+ || match v.get(key.clone().into_flat()) {
Ok(Some(v)) => Ok(v),
#[cfg(not(feature = "friendly-errors"))]
Ok(None) => throw!(NoSuchField(key.clone(), vec![])),
@@ -476,7 +476,10 @@
#[cfg(feature = "exp-preserve-order")]
false,
) {
- let conf = strsim::jaro_winkler(&field as &str, &key as &str);
+ let conf = strsim::jaro_winkler(
+ &field as &str,
+ &key.clone().into_flat() as &str,
+ );
if conf < 0.8 {
continue;
}
@@ -485,7 +488,7 @@
heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
throw!(NoSuchField(
- key.clone(),
+ key.clone().into_flat(),
heap.into_iter().map(|(_, v)| v).collect()
))
}
@@ -505,7 +508,7 @@
v.get(n as usize)?
.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?
}
- (Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),
+ (Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n.into_flat())),
(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(
ValType::Arr,
ValType::Num,
@@ -514,16 +517,18 @@
(Val::Str(s), Val::Num(n)) => Val::Str({
let v: IStr = s
+ .clone()
+ .into_flat()
.chars()
.skip(n as usize)
.take(1)
.collect::<String>()
.into();
if v.is_empty() {
- let size = s.chars().count();
+ let size = s.into_flat().chars().count();
throw!(StringBoundsError(n as usize, size))
}
- v
+ StrValue::Flat(v)
}),
(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(
ValType::Str,
@@ -654,7 +659,7 @@
|| format!("import {:?}", path.clone()),
|| s.import_resolved(resolved_path),
)?,
- ImportStr(_) => Val::Str(s.import_resolved_str(resolved_path)?),
+ ImportStr(_) => Val::Str(StrValue::Flat(s.import_resolved_str(resolved_path)?)),
ImportBin(_) => Val::Arr(ArrValue::bytes(s.import_resolved_bin(resolved_path)?)),
_ => unreachable!(),
}
crates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -3,8 +3,14 @@
use jrsonnet_parser::{BinaryOpType, LocExpr, UnaryOpType};
use crate::{
- arr::ArrValue, error::ErrorKind::*, evaluate, stdlib::std_format, throw, typed::Typed,
- val::equals, Context, Result, Val,
+ arr::ArrValue,
+ error::ErrorKind::*,
+ evaluate,
+ stdlib::std_format,
+ throw,
+ typed::Typed,
+ val::{equals, StrValue},
+ Context, Result, Val,
};
pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result<Val> {
@@ -25,15 +31,21 @@
Ok(match (a, b) {
(Str(a), Str(b)) if a.is_empty() => Val::Str(b.clone()),
(Str(a), Str(b)) if b.is_empty() => Val::Str(a.clone()),
- (Str(v1), Str(v2)) => Str(((**v1).to_owned() + v2).into()),
+ (Str(v1), Str(v2)) => Str(StrValue::concat(v1.clone(), v2.clone())),
// Can't use generic json serialization way, because it depends on number to string concatenation (std.jsonnet:890)
- (Num(a), Str(b)) => Str(format!("{a}{b}").into()),
- (Str(a), Num(b)) => Str(format!("{a}{b}").into()),
+ (Num(a), Str(b)) => Str(StrValue::Flat(format!("{a}{b}").into())),
+ (Str(a), Num(b)) => Str(StrValue::Flat(format!("{a}{b}").into())),
- (Str(a), o) | (o, Str(a)) if a.is_empty() => Val::Str(o.clone().to_string()?),
- (Str(a), o) => Str(format!("{a}{}", o.clone().to_string()?).into()),
- (o, Str(a)) => Str(format!("{}{a}", o.clone().to_string()?).into()),
+ (Str(a), o) | (o, Str(a)) if a.is_empty() => {
+ Val::Str(StrValue::Flat(o.clone().to_string()?))
+ }
+ (Str(a), o) => Str(StrValue::Flat(
+ format!("{a}{}", o.clone().to_string()?).into(),
+ )),
+ (o, Str(a)) => Str(StrValue::Flat(
+ format!("{}{a}", o.clone().to_string()?).into(),
+ )),
(Obj(v1), Obj(v2)) => Obj(v2.extend_from(v1.clone())),
(Arr(a), Arr(b)) => Val::Arr(ArrValue::extended(a.clone(), b.clone())),
@@ -56,7 +68,9 @@
}
Ok(Num(a % b))
}
- (Str(str), vals) => String::into_untyped(std_format(str.clone(), vals.clone())?),
+ (Str(str), vals) => {
+ String::into_untyped(std_format(&str.clone().into_flat(), vals.clone())?)
+ }
(a, b) => throw!(BinaryOperatorDoesNotOperateOnValues(
BinaryOpType::Mod,
a.value_type(),
@@ -120,10 +134,10 @@
(a, Lte, b) => Bool(evaluate_compare_op(a, b, Lte)?.is_le()),
(a, Gte, b) => Bool(evaluate_compare_op(a, b, Gte)?.is_ge()),
- (Str(a), In, Obj(obj)) => Bool(obj.has_field_ex(a.clone(), true)),
+ (Str(a), In, Obj(obj)) => Bool(obj.has_field_ex(a.clone().into_flat(), true)),
(a, Mod, b) => evaluate_mod_op(a, b)?,
- (Str(v1), Mul, Num(v2)) => Str(v1.repeat(*v2 as usize).into()),
+ (Str(v1), Mul, Num(v2)) => Str(StrValue::Flat(v1.to_string().repeat(*v2 as usize).into())),
// Bool X Bool
(Bool(a), And, Bool(b)) => Bool(*a && *b),
crates/jrsonnet-evaluator/src/function/arglike.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/arglike.rs
+++ b/crates/jrsonnet-evaluator/src/function/arglike.rs
@@ -4,7 +4,13 @@
use jrsonnet_parser::{ArgsDesc, LocExpr};
use crate::{
- error::Result, evaluate, gc::GcHashMap, tb, typed::Typed, val::ThunkValue, Context, Thunk, Val,
+ error::Result,
+ evaluate,
+ gc::GcHashMap,
+ tb,
+ typed::Typed,
+ val::{StrValue, ThunkValue},
+ Context, Thunk, Val,
};
/// Marker for arguments, which can be evaluated with context set to None
@@ -59,7 +65,7 @@
impl ArgLike for TlaArg {
fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {
match self {
- TlaArg::String(s) => Ok(Thunk::evaluated(Val::Str(s.clone()))),
+ TlaArg::String(s) => Ok(Thunk::evaluated(Val::Str(StrValue::Flat(s.clone())))),
TlaArg::Code(code) => Ok(if tailstrict {
Thunk::evaluated(evaluate(ctx, code)?)
} else {
crates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -7,7 +7,7 @@
Deserialize, Serialize,
};
-use crate::{arr::ArrValue, error::Result, ObjValueBuilder, State, Val};
+use crate::{arr::ArrValue, error::Result, val::StrValue, ObjValueBuilder, State, Val};
impl<'de> Deserialize<'de> for Val {
fn deserialize<D>(deserializer: D) -> Result<Val, D::Error>
@@ -49,7 +49,7 @@
where
E: serde::de::Error,
{
- Ok(Val::Str(v.into()))
+ Ok(Val::Str(StrValue::Flat(v.into())))
}
// visit_num! {
@@ -152,7 +152,7 @@
match self {
Val::Bool(v) => serializer.serialize_bool(*v),
Val::Null => serializer.serialize_none(),
- Val::Str(s) => serializer.serialize_str(s),
+ Val::Str(s) => serializer.serialize_str(&s.clone().into_flat()),
Val::Num(n) => serializer.serialize_f64(*n),
Val::Arr(arr) => {
let mut seq = serializer.serialize_seq(Some(arr.len()))?;
crates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/manifest.rs
@@ -147,7 +147,7 @@
}
}
Val::Null => buf.push_str("null"),
- Val::Str(s) => escape_string_json_buf(s, buf),
+ Val::Str(s) => escape_string_json_buf(&s.clone().into_flat(), buf),
Val::Num(n) => write!(buf, "{n}").unwrap(),
Val::Arr(items) => {
buf.push('[');
@@ -256,7 +256,7 @@
let Val::Str(s) = val else {
throw!("output should be string for string manifest format, got {}", val.value_type())
};
- out.write_str(&s).unwrap();
+ write!(out, "{s}").unwrap();
Ok(())
}
}
crates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/stdlib/format.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/format.rs
@@ -589,6 +589,7 @@
.ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,
),
Val::Str(s) => {
+ let s = s.into_flat();
if s.chars().count() != 1 {
throw!("%c expected 1 char string, got {}", s.chars().count(),);
}
crates/jrsonnet-evaluator/src/stdlib/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/stdlib/mod.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/mod.rs
@@ -2,13 +2,12 @@
#![allow(clippy::unnecessary_wraps)]
use format::{format_arr, format_obj};
-use jrsonnet_interner::IStr;
use crate::{error::Result, function::CallLocation, State, Val};
pub mod format;
-pub fn std_format(str: IStr, vals: Val) -> Result<String> {
+pub fn std_format(str: &str, vals: Val) -> Result<String> {
State::push(
CallLocation::native(),
|| format!("std.format of {str}"),
crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -11,7 +11,7 @@
function::{native::NativeDesc, FuncDesc, FuncVal},
throw,
typed::CheckType,
- val::IndexableVal,
+ val::{IndexableVal, StrValue},
ObjValue, ObjValueBuilder, Val,
};
@@ -187,13 +187,13 @@
const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);
fn into_untyped(value: Self) -> Result<Val> {
- Ok(Val::Str(value))
+ Ok(Val::Str(StrValue::Flat(value)))
}
fn from_untyped(value: Val) -> Result<Self> {
<Self as Typed>::TYPE.check(&value)?;
match value {
- Val::Str(s) => Ok(s),
+ Val::Str(s) => Ok(s.into_flat()),
_ => unreachable!(),
}
}
@@ -203,7 +203,7 @@
const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Str);
fn into_untyped(value: Self) -> Result<Val> {
- Ok(Val::Str(value.into()))
+ Ok(Val::Str(StrValue::Flat(value.into())))
}
fn from_untyped(value: Val) -> Result<Self> {
@@ -219,13 +219,13 @@
const TYPE: &'static ComplexValType = &ComplexValType::Char;
fn into_untyped(value: Self) -> Result<Val> {
- Ok(Val::Str(value.to_string().into()))
+ Ok(Val::Str(StrValue::Flat(value.to_string().into())))
}
fn from_untyped(value: Val) -> Result<Self> {
<Self as Typed>::TYPE.check(&value)?;
match value {
- Val::Str(s) => Ok(s.chars().next().unwrap()),
+ Val::Str(s) => Ok(s.into_flat().chars().next().unwrap()),
_ => unreachable!(),
}
}
@@ -480,7 +480,7 @@
fn into_untyped(value: Self) -> Result<Val> {
match value {
- IndexableVal::Str(s) => Ok(Val::Str(s)),
+ IndexableVal::Str(s) => Ok(Val::Str(StrValue::Flat(s))),
IndexableVal::Arr(a) => Ok(Val::Arr(a)),
}
}
crates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -150,7 +150,7 @@
Self::Any => Ok(()),
Self::Simple(t) => t.check(value),
Self::Char => match value {
- Val::Str(s) if s.len() == 1 || s.chars().count() == 1 => Ok(()),
+ Val::Str(s) if s.len() == 1 || s.clone().into_flat().chars().count() == 1 => Ok(()),
v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),
},
Self::BoundedNumber(from, to) => {
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use std::{cell::RefCell, fmt::Debug, mem::replace};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_types::ValType;67pub use crate::arr::ArrValue;8use crate::{9 error::{Error, ErrorKind::*},10 function::FuncVal,11 gc::{GcHashMap, TraceBox},12 manifest::{ManifestFormat, ToStringFormat},13 throw,14 typed::BoundedUsize,15 ObjValue, Result, Unbound, WeakObjValue,16};1718pub trait ThunkValue: Trace {19 type Output;20 fn get(self: Box<Self>) -> Result<Self::Output>;21}2223#[derive(Trace)]24enum ThunkInner<T: Trace> {25 Computed(T),26 Errored(Error),27 Waiting(TraceBox<dyn ThunkValue<Output = T>>),28 Pending,29}3031#[allow(clippy::module_name_repetitions)]32#[derive(Clone, Trace)]33pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);3435impl<T: Trace> Thunk<T> {36 pub fn evaluated(val: T) -> Self {37 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))38 }39 pub fn new(f: TraceBox<dyn ThunkValue<Output = T>>) -> Self {40 Self(Cc::new(RefCell::new(ThunkInner::Waiting(f))))41 }42 pub fn errored(e: Error) -> Self {43 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))44 }45}4647impl<T> Thunk<T>48where49 T: Clone + Trace,50{51 pub fn force(&self) -> Result<()> {52 self.evaluate()?;53 Ok(())54 }55 pub fn evaluate(&self) -> Result<T> {56 match &*self.0.borrow() {57 ThunkInner::Computed(v) => return Ok(v.clone()),58 ThunkInner::Errored(e) => return Err(e.clone()),59 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),60 ThunkInner::Waiting(..) => (),61 };62 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) else {63 unreachable!();64 };65 let new_value = match value.0.get() {66 Ok(v) => v,67 Err(e) => {68 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());69 return Err(e);70 }71 };72 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());73 Ok(new_value)74 }75}7677type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);7879#[derive(Trace, Clone)]80pub struct CachedUnbound<I, T>81where82 I: Unbound<Bound = T>,83 T: Trace,84{85 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,86 value: I,87}88impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {89 pub fn new(value: I) -> Self {90 Self {91 cache: Cc::new(RefCell::new(GcHashMap::new())),92 value,93 }94 }95}96impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {97 type Bound = T;98 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {99 let cache_key = (100 sup.as_ref().map(|s| s.clone().downgrade()),101 this.as_ref().map(|t| t.clone().downgrade()),102 );103 {104 if let Some(t) = self.cache.borrow().get(&cache_key) {105 return Ok(t.clone());106 }107 }108 let bound = self.value.bind(sup, this)?;109110 {111 let mut cache = self.cache.borrow_mut();112 cache.insert(cache_key, bound.clone());113 }114115 Ok(bound)116 }117}118119impl<T: Debug + Trace> Debug for Thunk<T> {120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {121 write!(f, "Lazy")122 }123}124impl<T: Trace> PartialEq for Thunk<T> {125 fn eq(&self, other: &Self) -> bool {126 Cc::ptr_eq(&self.0, &other.0)127 }128}129130/// Represents a Jsonnet value, which can be spliced or indexed (string or array).131#[allow(clippy::module_name_repetitions)]132pub enum IndexableVal {133 /// String.134 Str(IStr),135 /// Array.136 Arr(ArrValue),137}138impl IndexableVal {139 /// Slice the value.140 ///141 /// # Implementation142 ///143 /// For strings, will create a copy of specified interval.144 ///145 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.146 pub fn slice(147 self,148 index: Option<BoundedUsize<0, { i32::MAX as usize }>>,149 end: Option<BoundedUsize<0, { i32::MAX as usize }>>,150 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,151 ) -> Result<Self> {152 match &self {153 IndexableVal::Str(s) => {154 let index = index.as_deref().copied().unwrap_or(0);155 let end = end.as_deref().copied().unwrap_or(usize::MAX);156 let step = step.as_deref().copied().unwrap_or(1);157158 if index >= end {159 return Ok(Self::Str("".into()));160 }161162 Ok(Self::Str(163 (s.chars()164 .skip(index)165 .take(end - index)166 .step_by(step)167 .collect::<String>())168 .into(),169 ))170 }171 IndexableVal::Arr(arr) => {172 let index = index.as_deref().copied().unwrap_or(0);173 let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());174 let step = step.as_deref().copied().unwrap_or(1);175176 if index >= end {177 return Ok(Self::Arr(ArrValue::empty()));178 }179180 Ok(Self::Arr(181 arr.clone()182 .slice(Some(index), Some(end), Some(step))183 .expect("arguments checked"),184 ))185 }186 }187 }188}189190/// Represents any valid Jsonnet value.191#[derive(Debug, Clone, Trace)]192pub enum Val {193 /// Represents a Jsonnet boolean.194 Bool(bool),195 /// Represents a Jsonnet null value.196 Null,197 /// Represents a Jsonnet string.198 Str(IStr),199 /// Represents a Jsonnet number.200 /// Should be finite, and not NaN201 /// This restriction isn't enforced by enum, as enum field can't be marked as private202 Num(f64),203 /// Represents a Jsonnet array.204 Arr(ArrValue),205 /// Represents a Jsonnet object.206 Obj(ObjValue),207 /// Represents a Jsonnet function.208 Func(FuncVal),209}210211impl From<IndexableVal> for Val {212 fn from(v: IndexableVal) -> Self {213 match v {214 IndexableVal::Str(s) => Self::Str(s),215 IndexableVal::Arr(a) => Self::Arr(a),216 }217 }218}219220impl Val {221 pub const fn as_bool(&self) -> Option<bool> {222 match self {223 Self::Bool(v) => Some(*v),224 _ => None,225 }226 }227 pub const fn as_null(&self) -> Option<()> {228 match self {229 Self::Null => Some(()),230 _ => None,231 }232 }233 pub fn as_str(&self) -> Option<IStr> {234 match self {235 Self::Str(s) => Some(s.clone()),236 _ => None,237 }238 }239 pub const fn as_num(&self) -> Option<f64> {240 match self {241 Self::Num(n) => Some(*n),242 _ => None,243 }244 }245 pub fn as_arr(&self) -> Option<ArrValue> {246 match self {247 Self::Arr(a) => Some(a.clone()),248 _ => None,249 }250 }251 pub fn as_obj(&self) -> Option<ObjValue> {252 match self {253 Self::Obj(o) => Some(o.clone()),254 _ => None,255 }256 }257 pub fn as_func(&self) -> Option<FuncVal> {258 match self {259 Self::Func(f) => Some(f.clone()),260 _ => None,261 }262 }263264 /// Creates `Val::Num` after checking for numeric overflow.265 /// As numbers are `f64`, we can just check for their finity.266 pub fn new_checked_num(num: f64) -> Result<Self> {267 if num.is_finite() {268 Ok(Self::Num(num))269 } else {270 throw!("overflow")271 }272 }273274 pub const fn value_type(&self) -> ValType {275 match self {276 Self::Str(..) => ValType::Str,277 Self::Num(..) => ValType::Num,278 Self::Arr(..) => ValType::Arr,279 Self::Obj(..) => ValType::Obj,280 Self::Bool(_) => ValType::Bool,281 Self::Null => ValType::Null,282 Self::Func(..) => ValType::Func,283 }284 }285286 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {287 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {288 manifest.manifest(val.clone())289 }290 manifest_dyn(self, &format)291 }292293 pub fn to_string(&self) -> Result<IStr> {294 Ok(match self {295 Self::Bool(true) => "true".into(),296 Self::Bool(false) => "false".into(),297 Self::Null => "null".into(),298 Self::Str(s) => s.clone(),299 _ => self.manifest(ToStringFormat).map(IStr::from)?,300 })301 }302303 pub fn into_indexable(self) -> Result<IndexableVal> {304 Ok(match self {305 Val::Str(s) => IndexableVal::Str(s),306 Val::Arr(arr) => IndexableVal::Arr(arr),307 _ => throw!(ValueIsNotIndexable(self.value_type())),308 })309 }310}311312const fn is_function_like(val: &Val) -> bool {313 matches!(val, Val::Func(_))314}315316/// Native implementation of `std.primitiveEquals`317pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {318 Ok(match (val_a, val_b) {319 (Val::Bool(a), Val::Bool(b)) => a == b,320 (Val::Null, Val::Null) => true,321 (Val::Str(a), Val::Str(b)) => a == b,322 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,323 (Val::Arr(_), Val::Arr(_)) => {324 throw!("primitiveEquals operates on primitive types, got array")325 }326 (Val::Obj(_), Val::Obj(_)) => {327 throw!("primitiveEquals operates on primitive types, got object")328 }329 (a, b) if is_function_like(a) && is_function_like(b) => {330 throw!("cannot test equality of functions")331 }332 (_, _) => false,333 })334}335336/// Native implementation of `std.equals`337pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {338 if val_a.value_type() != val_b.value_type() {339 return Ok(false);340 }341 match (val_a, val_b) {342 (Val::Arr(a), Val::Arr(b)) => {343 if ArrValue::ptr_eq(a, b) {344 return Ok(true);345 }346 if a.len() != b.len() {347 return Ok(false);348 }349 for (a, b) in a.iter().zip(b.iter()) {350 if !equals(&a?, &b?)? {351 return Ok(false);352 }353 }354 Ok(true)355 }356 (Val::Obj(a), Val::Obj(b)) => {357 if ObjValue::ptr_eq(a, b) {358 return Ok(true);359 }360 let fields = a.fields(361 #[cfg(feature = "exp-preserve-order")]362 false,363 );364 if fields365 != b.fields(366 #[cfg(feature = "exp-preserve-order")]367 false,368 ) {369 return Ok(false);370 }371 for field in fields {372 if !equals(373 &a.get(field.clone())?.expect("field exists"),374 &b.get(field)?.expect("field exists"),375 )? {376 return Ok(false);377 }378 }379 Ok(true)380 }381 (a, b) => Ok(primitive_equals(a, b)?),382 }383}1use 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;13use crate::{14 error::{Error, ErrorKind::*},15 function::FuncVal,16 gc::{GcHashMap, TraceBox},17 manifest::{ManifestFormat, ToStringFormat},18 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#[allow(clippy::module_name_repetitions)]37#[derive(Clone, Trace)]38pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);3940impl<T: Trace> Thunk<T> {41 pub fn evaluated(val: T) -> Self {42 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))43 }44 pub fn new(f: TraceBox<dyn ThunkValue<Output = T>>) -> Self {45 Self(Cc::new(RefCell::new(ThunkInner::Waiting(f))))46 }47 pub fn errored(e: Error) -> Self {48 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))49 }50}5152impl<T> Thunk<T>53where54 T: Clone + Trace,55{56 pub fn force(&self) -> Result<()> {57 self.evaluate()?;58 Ok(())59 }60 pub fn evaluate(&self) -> Result<T> {61 match &*self.0.borrow() {62 ThunkInner::Computed(v) => return Ok(v.clone()),63 ThunkInner::Errored(e) => return Err(e.clone()),64 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),65 ThunkInner::Waiting(..) => (),66 };67 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending) else {68 unreachable!();69 };70 let new_value = match value.0.get() {71 Ok(v) => v,72 Err(e) => {73 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());74 return Err(e);75 }76 };77 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());78 Ok(new_value)79 }80}8182type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);8384#[derive(Trace, Clone)]85pub struct CachedUnbound<I, T>86where87 I: Unbound<Bound = T>,88 T: Trace,89{90 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,91 value: I,92}93impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {94 pub fn new(value: I) -> Self {95 Self {96 cache: Cc::new(RefCell::new(GcHashMap::new())),97 value,98 }99 }100}101impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {102 type Bound = T;103 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {104 let cache_key = (105 sup.as_ref().map(|s| s.clone().downgrade()),106 this.as_ref().map(|t| t.clone().downgrade()),107 );108 {109 if let Some(t) = self.cache.borrow().get(&cache_key) {110 return Ok(t.clone());111 }112 }113 let bound = self.value.bind(sup, this)?;114115 {116 let mut cache = self.cache.borrow_mut();117 cache.insert(cache_key, bound.clone());118 }119120 Ok(bound)121 }122}123124impl<T: Debug + Trace> Debug for Thunk<T> {125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {126 write!(f, "Lazy")127 }128}129impl<T: Trace> PartialEq for Thunk<T> {130 fn eq(&self, other: &Self) -> bool {131 Cc::ptr_eq(&self.0, &other.0)132 }133}134135/// Represents a Jsonnet value, which can be spliced or indexed (string or array).136#[allow(clippy::module_name_repetitions)]137pub enum IndexableVal {138 /// String.139 Str(IStr),140 /// Array.141 Arr(ArrValue),142}143impl IndexableVal {144 /// Slice the value.145 ///146 /// # Implementation147 ///148 /// For strings, will create a copy of specified interval.149 ///150 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.151 pub fn slice(152 self,153 index: Option<BoundedUsize<0, { i32::MAX as usize }>>,154 end: Option<BoundedUsize<0, { i32::MAX as usize }>>,155 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,156 ) -> Result<Self> {157 match &self {158 IndexableVal::Str(s) => {159 let index = index.as_deref().copied().unwrap_or(0);160 let end = end.as_deref().copied().unwrap_or(usize::MAX);161 let step = step.as_deref().copied().unwrap_or(1);162163 if index >= end {164 return Ok(Self::Str("".into()));165 }166167 Ok(Self::Str(168 (s.chars()169 .skip(index)170 .take(end - index)171 .step_by(step)172 .collect::<String>())173 .into(),174 ))175 }176 IndexableVal::Arr(arr) => {177 let index = index.as_deref().copied().unwrap_or(0);178 let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());179 let step = step.as_deref().copied().unwrap_or(1);180181 if index >= end {182 return Ok(Self::Arr(ArrValue::empty()));183 }184185 Ok(Self::Arr(186 arr.clone()187 .slice(Some(index), Some(end), Some(step))188 .expect("arguments checked"),189 ))190 }191 }192 }193}194195#[derive(Debug, Clone, Trace)]196pub enum StrValue {197 Flat(IStr),198 Tree(Rc<(StrValue, StrValue, usize)>),199}200impl StrValue {201 pub fn concat(a: StrValue, b: StrValue) -> Self {202 if a.is_empty() {203 b204 } else if b.is_empty() {205 a206 } else {207 let len = a.len() + b.len();208 Self::Tree(Rc::new((a, b, len)))209 }210 }211 pub fn into_flat(self) -> IStr {212 match self {213 StrValue::Flat(f) => f,214 StrValue::Tree(_) => {215 let mut buf = String::new();216 self.into_flat_buf(&mut buf);217 buf.into()218 }219 }220 }221 fn into_flat_buf(&self, out: &mut String) {222 match self {223 StrValue::Flat(f) => out.push_str(f),224 StrValue::Tree(t) => {225 t.0.into_flat_buf(out);226 t.1.into_flat_buf(out);227 }228 }229 }230 pub fn len(&self) -> usize {231 match self {232 StrValue::Flat(v) => v.len(),233 StrValue::Tree(t) => t.2,234 }235 }236 pub fn is_empty(&self) -> bool {237 match self {238 Self::Flat(v) => v.is_empty(),239 _ => false,240 }241 }242}243impl Display for StrValue {244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {245 match self {246 StrValue::Flat(v) => write!(f, "{v}"),247 StrValue::Tree(t) => {248 write!(f, "{}", t.0)?;249 write!(f, "{}", t.1)250 }251 }252 }253}254impl PartialEq for StrValue {255 fn eq(&self, other: &Self) -> bool {256 let a = self.clone().into_flat();257 let b = other.clone().into_flat();258 a == b259 }260}261impl Eq for StrValue {}262impl PartialOrd for StrValue {263 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {264 let a = self.clone().into_flat();265 let b = other.clone().into_flat();266 Some(a.cmp(&b))267 }268}269impl Ord for StrValue {270 fn cmp(&self, other: &Self) -> std::cmp::Ordering {271 self.partial_cmp(other)272 .expect("partial_cmp always returns Some")273 }274}275276/// Represents any valid Jsonnet value.277#[derive(Debug, Clone, Trace)]278pub enum Val {279 /// Represents a Jsonnet boolean.280 Bool(bool),281 /// Represents a Jsonnet null value.282 Null,283 /// Represents a Jsonnet string.284 Str(StrValue),285 /// Represents a Jsonnet number.286 /// Should be finite, and not NaN287 /// This restriction isn't enforced by enum, as enum field can't be marked as private288 Num(f64),289 /// Represents a Jsonnet array.290 Arr(ArrValue),291 /// Represents a Jsonnet object.292 Obj(ObjValue),293 /// Represents a Jsonnet function.294 Func(FuncVal),295}296297static_assertions::assert_eq_size!(Val, [u8; 24]);298299impl From<IndexableVal> for Val {300 fn from(v: IndexableVal) -> Self {301 match v {302 IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),303 IndexableVal::Arr(a) => Self::Arr(a),304 }305 }306}307308impl Val {309 pub const fn as_bool(&self) -> Option<bool> {310 match self {311 Self::Bool(v) => Some(*v),312 _ => None,313 }314 }315 pub const fn as_null(&self) -> Option<()> {316 match self {317 Self::Null => Some(()),318 _ => None,319 }320 }321 pub fn as_str(&self) -> Option<IStr> {322 match self {323 Self::Str(s) => Some(s.clone().into_flat()),324 _ => None,325 }326 }327 pub const fn as_num(&self) -> Option<f64> {328 match self {329 Self::Num(n) => Some(*n),330 _ => None,331 }332 }333 pub fn as_arr(&self) -> Option<ArrValue> {334 match self {335 Self::Arr(a) => Some(a.clone()),336 _ => None,337 }338 }339 pub fn as_obj(&self) -> Option<ObjValue> {340 match self {341 Self::Obj(o) => Some(o.clone()),342 _ => None,343 }344 }345 pub fn as_func(&self) -> Option<FuncVal> {346 match self {347 Self::Func(f) => Some(f.clone()),348 _ => None,349 }350 }351352 /// Creates `Val::Num` after checking for numeric overflow.353 /// As numbers are `f64`, we can just check for their finity.354 pub fn new_checked_num(num: f64) -> Result<Self> {355 if num.is_finite() {356 Ok(Self::Num(num))357 } else {358 throw!("overflow")359 }360 }361362 pub const fn value_type(&self) -> ValType {363 match self {364 Self::Str(..) => ValType::Str,365 Self::Num(..) => ValType::Num,366 Self::Arr(..) => ValType::Arr,367 Self::Obj(..) => ValType::Obj,368 Self::Bool(_) => ValType::Bool,369 Self::Null => ValType::Null,370 Self::Func(..) => ValType::Func,371 }372 }373374 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {375 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {376 manifest.manifest(val.clone())377 }378 manifest_dyn(self, &format)379 }380381 pub fn to_string(&self) -> Result<IStr> {382 Ok(match self {383 Self::Bool(true) => "true".into(),384 Self::Bool(false) => "false".into(),385 Self::Null => "null".into(),386 Self::Str(s) => s.clone().into_flat(),387 _ => self.manifest(ToStringFormat).map(IStr::from)?,388 })389 }390391 pub fn into_indexable(self) -> Result<IndexableVal> {392 Ok(match self {393 Val::Str(s) => IndexableVal::Str(s.into_flat()),394 Val::Arr(arr) => IndexableVal::Arr(arr),395 _ => throw!(ValueIsNotIndexable(self.value_type())),396 })397 }398}399400const fn is_function_like(val: &Val) -> bool {401 matches!(val, Val::Func(_))402}403404/// Native implementation of `std.primitiveEquals`405pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {406 Ok(match (val_a, val_b) {407 (Val::Bool(a), Val::Bool(b)) => a == b,408 (Val::Null, Val::Null) => true,409 (Val::Str(a), Val::Str(b)) => a == b,410 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,411 (Val::Arr(_), Val::Arr(_)) => {412 throw!("primitiveEquals operates on primitive types, got array")413 }414 (Val::Obj(_), Val::Obj(_)) => {415 throw!("primitiveEquals operates on primitive types, got object")416 }417 (a, b) if is_function_like(a) && is_function_like(b) => {418 throw!("cannot test equality of functions")419 }420 (_, _) => false,421 })422}423424/// Native implementation of `std.equals`425pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {426 if val_a.value_type() != val_b.value_type() {427 return Ok(false);428 }429 match (val_a, val_b) {430 (Val::Arr(a), Val::Arr(b)) => {431 if ArrValue::ptr_eq(a, b) {432 return Ok(true);433 }434 if a.len() != b.len() {435 return Ok(false);436 }437 for (a, b) in a.iter().zip(b.iter()) {438 if !equals(&a?, &b?)? {439 return Ok(false);440 }441 }442 Ok(true)443 }444 (Val::Obj(a), Val::Obj(b)) => {445 if ObjValue::ptr_eq(a, b) {446 return Ok(true);447 }448 let fields = a.fields(449 #[cfg(feature = "exp-preserve-order")]450 false,451 );452 if fields453 != b.fields(454 #[cfg(feature = "exp-preserve-order")]455 false,456 ) {457 return Ok(false);458 }459 for field in fields {460 if !equals(461 &a.get(field.clone())?.expect("field exists"),462 &b.get(field)?.expect("field exists"),463 )? {464 return Ok(false);465 }466 }467 Ok(true)468 }469 (a, b) => Ok(primitive_equals(a, b)?),470 }471}crates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/arrays.rs
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -37,12 +37,13 @@
func: NativeFn<((Either![String, Any],), Any)>,
arr: IndexableVal,
) -> Result<IndexableVal> {
+ use std::fmt::Write;
match arr {
IndexableVal::Str(str) => {
let mut out = String::new();
for c in str.chars() {
match func(Either2::A(c.to_string()))?.0 {
- Val::Str(o) => out.push_str(&o),
+ Val::Str(o) => write!(out, "{o}").unwrap(),
Val::Null => continue,
_ => throw!("in std.join all items should be strings"),
};
@@ -101,6 +102,7 @@
#[builtin]
pub fn builtin_join(sep: IndexableVal, arr: ArrValue) -> Result<IndexableVal> {
+ use std::fmt::Write;
Ok(match sep {
IndexableVal::Arr(joiner_items) => {
let mut out = Vec::new();
@@ -141,7 +143,7 @@
out += &sep;
}
first = false;
- out += &item;
+ write!(out, "{item}").unwrap()
} else if matches!(item, Val::Null) {
continue;
} else {
crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -320,15 +320,19 @@
}
#[cfg(feature = "legacy-this-file")]
fn initialize(&self, s: State, source: Source) -> Context {
+ use jrsonnet_evaluator::val::StrValue;
+
let mut builder = ObjValueBuilder::new();
builder.with_super(self.stdlib_obj.clone());
builder
.member("thisFile".into())
.hide()
- .value(Val::Str(match source.source_path().path() {
- Some(p) => self.settings().path_resolver.resolve(p).into(),
- None => source.source_path().to_string().into(),
- }))
+ .value(Val::Str(StrValue::Flat(
+ match source.source_path().path() {
+ Some(p) => self.settings().path_resolver.resolve(p).into(),
+ None => source.source_path().to_string().into(),
+ },
+ )))
.expect("this object builder is empty");
let stdlib_with_this_file = builder.build();
crates/jrsonnet-stdlib/src/manifest/yaml.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/manifest/yaml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/yaml.rs
@@ -118,6 +118,7 @@
}
Val::Null => buf.push_str("null"),
Val::Str(s) => {
+ let s = s.clone().into_flat();
if s.is_empty() {
buf.push_str("\"\"");
} else if let Some(s) = s.strip_suffix('\n') {
@@ -128,10 +129,10 @@
buf.push_str(&options.padding);
buf.push_str(line);
}
- } else if !options.quote_keys && !yaml_needs_quotes(s) {
- buf.push_str(s);
+ } else if !options.quote_keys && !yaml_needs_quotes(&s) {
+ buf.push_str(&s);
} else {
- escape_string_json_buf(s, buf);
+ escape_string_json_buf(&s, buf);
}
}
Val::Num(n) => write!(buf, "{}", *n).unwrap(),
crates/jrsonnet-stdlib/src/objects.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/objects.rs
+++ b/crates/jrsonnet-stdlib/src/objects.rs
@@ -1,5 +1,9 @@
use jrsonnet_evaluator::{
- error::Result, function::builtin, typed::VecVal, val::Val, IStr, ObjValue,
+ error::Result,
+ function::builtin,
+ typed::VecVal,
+ val::{StrValue, Val},
+ IStr, ObjValue,
};
use jrsonnet_gcmodule::Cc;
@@ -17,7 +21,10 @@
preserve_order,
);
Ok(VecVal(Cc::new(
- out.into_iter().map(Val::Str).collect::<Vec<_>>(),
+ out.into_iter()
+ .map(StrValue::Flat)
+ .map(Val::Str)
+ .collect::<Vec<_>>(),
)))
}
crates/jrsonnet-stdlib/src/operator.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/operator.rs
+++ b/crates/jrsonnet-stdlib/src/operator.rs
@@ -7,7 +7,7 @@
operator::evaluate_mod_op,
stdlib::std_format,
typed::{Any, Either, Either2},
- val::{equals, primitive_equals},
+ val::{equals, primitive_equals, StrValue},
IStr, Val,
};
@@ -17,7 +17,7 @@
Ok(Any(evaluate_mod_op(
&match a {
A(v) => Val::Num(v),
- B(s) => Val::Str(s),
+ B(s) => Val::Str(StrValue::Flat(s)),
},
&b.0,
)?))
@@ -35,5 +35,5 @@
#[builtin]
pub fn builtin_format(str: IStr, vals: Any) -> Result<String> {
- std_format(str, vals.0)
+ std_format(&str, vals.0)
}
crates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -3,7 +3,7 @@
function::builtin,
throw,
typed::{Either2, VecVal, M1},
- val::ArrValue,
+ val::{ArrValue, StrValue},
Either, IStr, Val,
};
use jrsonnet_gcmodule::Cc;
@@ -34,9 +34,12 @@
Ok(VecVal(Cc::new(match maxsplits {
A(n) => str
.splitn(n + 1, &c as &str)
- .map(|s| Val::Str(s.into()))
+ .map(|s| Val::Str(StrValue::Flat(s.into())))
+ .collect(),
+ B(_) => str
+ .split(&c as &str)
+ .map(|s| Val::Str(StrValue::Flat(s.into())))
.collect(),
- B(_) => str.split(&c as &str).map(|s| Val::Str(s.into())).collect(),
})))
}