difftreelog
refactor trivial arrays
in: master
5 files changed
crates/jrsonnet-evaluator/src/analyze.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/analyze.rs
+++ b/crates/jrsonnet-evaluator/src/analyze.rs
@@ -138,14 +138,12 @@
#[derive(Debug, Acyclic)]
pub enum LExpr {
Slot(LSlot),
- Null,
- Bool(bool),
- Str(IStr),
- Num(NumValue),
+ Trivial(TrivialVal),
Arr {
shape: ClosureShape,
items: Rc<Vec<LExpr>>,
},
+ ArrConst(Rc<Vec<TrivialVal>>),
ArrComp(Box<LArrComp>),
Obj(LObjBody),
ObjExtend(Box<LExpr>, LObjBody),
@@ -1345,15 +1343,15 @@
#[allow(clippy::too_many_lines)]
pub fn analyze(expr: &Expr, stack: &mut AnalysisStack, taint: &mut AnalysisResult) -> LExpr {
match expr {
- Expr::Literal(span, l) => match l {
- LiteralType::This => stack.use_this(taint).map_or_else(
+ Expr::Identity(span, l) => match l {
+ IdentityKind::This => stack.use_this(taint).map_or_else(
|| {
stack.report_error("`self` used outside of object", Some(span.clone()));
LExpr::BadLocal("self")
},
LExpr::Slot,
),
- LiteralType::Super => {
+ IdentityKind::Super => {
if stack.use_super(taint).is_some() {
LExpr::Super
} else {
@@ -1361,25 +1359,34 @@
LExpr::BadLocal("super")
}
}
- LiteralType::Dollar => stack.use_dollar(taint).map_or_else(
+ IdentityKind::Dollar => stack.use_dollar(taint).map_or_else(
|| {
stack.report_error("`$` used outside of object", Some(span.clone()));
LExpr::BadLocal("$")
},
LExpr::Slot,
),
- LiteralType::Null => LExpr::Null,
- LiteralType::True => LExpr::Bool(true),
- LiteralType::False => LExpr::Bool(false),
},
- Expr::Str(s) => LExpr::Str(s.clone()),
- Expr::Num(n) => LExpr::Num(*n),
+ Expr::Trivial(tv) => LExpr::Trivial(tv.clone()),
Expr::Var(v) => stack
.use_local(&v.value, v.span.clone(), taint)
.map_or_else(|| LExpr::BadLocal("ref"), LExpr::Slot),
Expr::Arr(a) => {
- let (shape, items) = stack
- .in_using_closure(|stack| a.iter().map(|v| analyze(v, stack, taint)).collect());
+ if a.iter().all(|i| matches!(i, Expr::Trivial(_))) {
+ let trivials: Vec<_> = a
+ .iter()
+ .map(|i| match i {
+ Expr::Trivial(tv) => tv.clone(),
+ _ => unreachable!("checked above"),
+ })
+ .collect();
+ return LExpr::ArrConst(Rc::new(trivials));
+ }
+ let (shape, items) = stack.in_using_closure(|stack| {
+ a.iter()
+ .map(|v| analyze(v, stack, taint))
+ .collect::<Vec<_>>()
+ });
LExpr::Arr {
shape,
items: Rc::new(items),
@@ -1412,7 +1419,7 @@
}
Expr::LocalExpr(binds, body) => analyze_local_expr(binds, body, stack, taint),
Expr::Import(kind, path_expr) => {
- let Expr::Str(path) = &**path_expr else {
+ let Expr::Trivial(TrivialVal::Str(path)) = &**path_expr else {
stack.report_error(
"import path must be a string literal",
Some(kind.span.clone()),
crates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth1use std::{2 any::Any,3 fmt::{self},4 num::NonZeroU32,5 ops::{Bound, RangeBounds},6 rc::Rc,7};89use jrsonnet_gcmodule::{Cc, cc_dyn};1011use crate::{Context, Result, Thunk, Val, analyze::LExpr, function::NativeFn, typed::IntoUntyped};1213mod spec;14pub use spec::{ArrayLike, *};1516cc_dyn!(17 #[doc = "Represents a Jsonnet array value."]18 #[derive(Clone)]19 ArrValue,20 ArrayLike,21 pub fn new() {...}22);23impl fmt::Debug for ArrValue {24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {25 self.0.fmt(f)26 }27}2829pub trait ArrayLikeIter<T>: Iterator<Item = T> + DoubleEndedIterator + ExactSizeIterator {}30impl<I, T> ArrayLikeIter<T> for I where31 I: Iterator<Item = T> + DoubleEndedIterator + ExactSizeIterator32{33}3435impl ArrValue {36 pub fn empty() -> Self {37 Self::new(())38 }3940 pub fn expr(ctx: Context, exprs: Rc<Vec<LExpr>>) -> Self {41 Self::new(ExprArray::new(ctx, exprs))42 }4344 pub fn repeated(data: Self, repeats: u32) -> Option<Self> {45 Some(Self::new(RepeatedArray::new(data, repeats)?))46 }4748 pub fn make(len: u32, cb: NativeFn!((u32,)->Val)) -> Self {49 Self::new(MakeArray::new(len, cb))50 }5152 #[must_use]53 pub fn map(self, mapper: NativeFn!((Val) -> Val)) -> Self {54 Self::new(<MappedArray>::new(self, ArrayMapper::Plain(mapper)))55 }5657 #[must_use]58 pub fn map_with_index(self, mapper: NativeFn!((u32, Val) -> Val)) -> Self {59 Self::new(<MappedArray>::new(self, ArrayMapper::WithIndex(mapper)))60 }6162 pub fn filter(self, filter: NativeFn!((Thunk<Val>) -> bool)) -> Result<Self> {63 // TODO: ArrValue::Picked(inner, indexes) for large arrays64 'eager: {65 let mut out = Vec::new();66 for i in self.iter() {67 let Ok(i) = i else {68 break 'eager;69 };70 if filter.call(IntoUntyped::into_lazy_untyped(i.clone()))? {71 out.push(i);72 }73 }74 return Ok(Self::new(out));75 };7677 let mut out = Vec::new();78 for i in self.iter_lazy() {79 if filter.call(i.clone())? {80 out.push(i);81 }82 }83 Ok(Self::new(out))84 }8586 pub fn extended(a: Self, b: Self) -> Option<Self> {87 Some(if a.is_empty() {88 b89 } else if b.is_empty() {90 a91 } else {92 Self::new(ExtendedArray::new(a, b)?)93 })94 }9596 pub fn range_exclusive(a: i32, b: i32) -> Self {97 Self::new(RangeArray::new_exclusive(a, b))98 }99 pub fn range_inclusive(a: i32, b: i32) -> Self {100 Self::new(RangeArray::new_inclusive(a, b))101 }102103 #[inline]104 #[must_use]105 pub fn slice(self, range: impl RangeBounds<usize>) -> Self {106 fn map_bound(start: bool, bound: Bound<&usize>) -> Option<i32> {107 match bound {108 Bound::Included(&v) => Some(i32::try_from(v).unwrap_or(i32::MAX)),109 Bound::Excluded(&v) => Some(110 i32::try_from(v)111 .unwrap_or(i32::MAX)112 .saturating_add(if start { 1 } else { -1 }),113 ),114 Bound::Unbounded => None,115 }116 }117 self.slice32(118 map_bound(true, range.start_bound()),119 map_bound(false, range.end_bound()),120 None,121 )122 }123124 #[must_use]125 pub fn slice32(self, index: Option<i32>, end: Option<i32>, step: Option<NonZeroU32>) -> Self {126 let get_idx = |pos: Option<i32>, len: u32, default| match pos {127 Some(v) if v < 0 => len.saturating_add_signed(v),128 #[expect(clippy::cast_sign_loss, reason = "abs value is used")]129 Some(v) => (v as u32).min(len),130 None => default,131 };132 let index = get_idx(index, self.len32(), 0);133 let end = get_idx(end, self.len32(), self.len32());134 let step = step.unwrap_or_else(|| NonZeroU32::new(1).expect("1 != 0"));135136 if index >= end {137 return Self::empty();138 }139140 Self::new(SliceArray {141 inner: self,142 from: index,143 to: end,144 step: step.get(),145 })146 }147148 /// Array length.149 #[inline]150 pub fn len32(&self) -> u32 {151 self.0.len32()152 }153154 pub fn len(&self) -> usize {155 self.len32() as usize156 }157158 /// Is array contains no elements?159 #[inline]160 pub fn is_empty(&self) -> bool {161 self.0.is_empty()162 }163164 #[inline]165 pub fn is_cheap(&self) -> bool {166 self.0.is_cheap()167 }168169 /// Get array element by index, evaluating it, if it is lazy.170 ///171 /// Returns `None` on out-of-bounds condition.172 #[inline]173 pub fn get32(&self, index: u32) -> Result<Option<Val>> {174 self.0.get32(index)175 }176177 pub fn get(&self, index: usize) -> Result<Option<Val>> {178 let Ok(i) = u32::try_from(index) else {179 return Ok(None);180 };181 self.get32(i)182 }183184 /// Get array element by index, without evaluation.185 ///186 /// Returns `None` on out-of-bounds condition.187 #[inline]188 pub fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {189 self.0.get_lazy32(index)190 }191192 pub fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {193 u32::try_from(index).ok().and_then(|i| self.get_lazy32(i))194 }195196 pub fn iter(&self) -> impl ArrayLikeIter<Result<Val>> + '_ {197 (0..self.len32()).map(|i| self.get32(i).transpose().expect("length checked"))198 }199200 /// Iterate over elements, returning lazy values.201 pub fn iter_lazy(&self) -> impl ArrayLikeIter<Thunk<Val>> + '_ {202 (0..self.len32()).map(|i| self.get_lazy32(i).expect("length checked"))203 }204205 /// Return a reversed view on current array.206 #[must_use]207 pub fn reversed(self) -> Self {208 Self::new(ReverseArray(self))209 }210211 pub fn ptr_eq(a: &Self, b: &Self) -> bool {212 Cc::ptr_eq(&a.0, &b.0)213 }214215 pub fn as_any(&self) -> &dyn Any {216 &self.0217 }218}219impl<T> From<T> for ArrValue220where221 T: ArrayLike,222{223 fn from(value: T) -> Self {224 Self::new(value)225 }226}227impl<I> FromIterator<I> for ArrValue228where229 Vec<I>: ArrayLike,230{231 fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {232 Self::new(iter.into_iter().collect::<Vec<_>>())233 }234}235236/// Checks that the usize does not exceed 4g with debug assertions enabled237/// Should only be used on values that can't reasonably exceed this value238#[inline]239pub(crate) fn arridx(i: usize) -> u32 {240 #[allow(241 clippy::cast_possible_truncation,242 reason = "array indexes never exceed 4g"243 )]244 if cfg!(debug_assertions) {245 u32::try_from(i).expect("4g hard limit")246 } else {247 i as u32248 }249}crates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -8,11 +8,12 @@
use jrsonnet_gcmodule::{Cc, Trace};
use jrsonnet_interner::{IBytes, IStr};
+use jrsonnet_ir::TrivialVal;
use super::{ArrValue, arridx};
use crate::{
Context, Error, ObjValue, Result, Thunk, Val,
- analyze::{ClosureShape, LExpr},
+ analyze::LExpr,
error::ErrorKind::InfiniteRecursionDetected,
evaluate::evaluate,
function::NativeFn,
@@ -108,6 +109,18 @@
}
}
+impl ArrayCheap for Rc<Vec<TrivialVal>> {
+ fn get(&self, index: u32) -> Option<Val> {
+ self.as_slice()
+ .get(index as usize)
+ .map(|tv| tv.clone().into())
+ }
+
+ fn len(&self) -> u32 {
+ arridx(self.as_slice().len())
+ }
+}
+
#[derive(Debug, Trace, Clone)]
enum ArrayThunk {
Computed(Val),
@@ -123,9 +136,9 @@
cached: Cc<RefCell<Vec<ArrayThunk>>>,
}
impl ExprArray {
- pub fn new(outer: Context, shape: &ClosureShape, src: Rc<Vec<LExpr>>) -> Self {
+ pub fn new(ctx: Context, src: Rc<Vec<LExpr>>) -> Self {
Self {
- ctx: Context::enter_using(&outer, shape),
+ ctx,
cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),
src,
}
@@ -153,9 +166,17 @@
unreachable!()
};
- let new_value: Val = evaluate(self.ctx.clone(), &self.src[index as usize])?;
- self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());
- Ok(Some(new_value))
+ let result = evaluate(self.ctx.clone(), &self.src[index as usize]);
+ match result {
+ Ok(new_value) => {
+ self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());
+ Ok(Some(new_value))
+ }
+ Err(e) => {
+ self.cached.borrow_mut()[index as usize] = ArrayThunk::Waiting;
+ Err(e)
+ }
+ }
}
fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {
#[derive(Trace)]
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -52,15 +52,11 @@
}
pub fn evaluate_trivial(expr: &LExpr) -> Option<Val> {
- // TODO: Eager trivial array
- Some(match expr {
- LExpr::Str(s) => Val::string(s.clone()),
- LExpr::Num(n) => Val::Num(*n),
- LExpr::Bool(false) => Val::Bool(false),
- LExpr::Bool(true) => Val::Bool(true),
- LExpr::Null => Val::Null,
- _ => return None,
- })
+ if let LExpr::Trivial(tv) = expr {
+ Some(tv.clone().into())
+ } else {
+ None
+ }
}
pub fn evaluate_method(ctx: Context, name: IStr, func: &Rc<LFunction>) -> Val {
@@ -119,13 +115,24 @@
pub fn evaluate(mut ctx: Context, mut expr: &LExpr) -> Result<Val> {
loop {
return Ok(match expr {
- LExpr::Null => Val::Null,
- LExpr::Bool(b) => Val::Bool(*b),
- LExpr::Str(s) => Val::string(s.clone()),
- LExpr::Num(n) => Val::Num(*n),
+ LExpr::Trivial(tv) => tv.clone().into(),
LExpr::Slot(slot) => ctx.slot(*slot).evaluate()?,
LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),
- LExpr::Arr { shape, items } => Val::Arr(ArrValue::expr(ctx, shape, items.clone())),
+ LExpr::ArrConst(rc) => Val::Arr(ArrValue::new(rc.clone())),
+ LExpr::Arr { shape, items } => {
+ let inner = Context::enter_using(&ctx, shape);
+ 'eager: {
+ let mut out: Vec<Val> = Vec::with_capacity(items.len());
+ for item in items.iter() {
+ let Ok(r) = evaluate(inner.clone(), item) else {
+ break 'eager;
+ };
+ out.push(r);
+ }
+ return Ok(Val::Arr(ArrValue::new(out)));
+ }
+ Val::Arr(ArrValue::expr(inner, items.clone()))
+ }
LExpr::UnaryOp(op, value) => {
let value = evaluate(ctx, value)?;
evaluate_unary_op(*op, &value)?
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -10,7 +10,7 @@
use jrsonnet_gcmodule::{Acyclic, Cc, Trace, cc_dyn};
use jrsonnet_interner::IStr;
-use jrsonnet_ir::BinaryOpType;
+use jrsonnet_ir::{BinaryOpType, TrivialVal};
pub use jrsonnet_macros::Thunk;
use jrsonnet_types::ValType;
use rustc_hash::FxHashMap;
@@ -621,6 +621,16 @@
Self::Bool(value)
}
}
+impl From<TrivialVal> for Val {
+ fn from(tv: TrivialVal) -> Self {
+ match tv {
+ TrivialVal::Null => Self::Null,
+ TrivialVal::Bool(b) => Self::Bool(b),
+ TrivialVal::Num(n) => Self::Num(n),
+ TrivialVal::Str(s) => Self::string(s),
+ }
+ }
+}
const fn is_function_like(val: &Val) -> bool {
matches!(val, Val::Func(_))