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.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -8,12 +8,7 @@
use jrsonnet_gcmodule::{Cc, cc_dyn};
-use crate::{
- Context, Result, Thunk, Val,
- analyze::{ClosureShape, LExpr},
- function::NativeFn,
- typed::IntoUntyped,
-};
+use crate::{Context, Result, Thunk, Val, analyze::LExpr, function::NativeFn, typed::IntoUntyped};
mod spec;
pub use spec::{ArrayLike, *};
@@ -42,8 +37,8 @@
Self::new(())
}
- pub fn expr(ctx: Context, shape: &ClosureShape, exprs: Rc<Vec<LExpr>>) -> Self {
- Self::new(ExprArray::new(ctx, shape, exprs))
+ pub fn expr(ctx: Context, exprs: Rc<Vec<LExpr>>) -> Self {
+ Self::new(ExprArray::new(ctx, exprs))
}
pub fn repeated(data: Self, repeats: u32) -> Option<Self> {
crates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth1use std::{2 any::Any,3 cell::RefCell,4 fmt::{self, Debug},5 mem::replace,6 rc::Rc,7};89use jrsonnet_gcmodule::{Cc, Trace};10use jrsonnet_interner::{IBytes, IStr};1112use super::{ArrValue, arridx};13use crate::{14 Context, Error, ObjValue, Result, Thunk, Val,15 analyze::{ClosureShape, LExpr},16 error::ErrorKind::InfiniteRecursionDetected,17 evaluate::evaluate,18 function::NativeFn,19 typed::{IntoUntyped, Typed},20 val::ThunkValue,21};2223pub trait ArrayLike: Any + Trace + Debug {24 fn len32(&self) -> u32;25 fn is_empty(&self) -> bool {26 self.len32() == 027 }28 fn get32(&self, index: u32) -> Result<Option<Val>>;29 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>>;3031 fn is_cheap(&self) -> bool {32 false33 }34}35trait ArrayCheap {36 fn get(&self, index: u32) -> Option<Val>;37 fn len(&self) -> u32;38}39impl<T> ArrayLike for T40where41 T: Any + Trace + Debug + ArrayCheap,42{43 fn len32(&self) -> u32 {44 <T as ArrayCheap>::len(self)45 }4647 fn get32(&self, index: u32) -> Result<Option<Val>> {48 Ok(<T as ArrayCheap>::get(self, index))49 }5051 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {52 <T as ArrayCheap>::get(self, index).map(Thunk::evaluated)53 }5455 fn is_cheap(&self) -> bool {56 true57 }58}5960impl ArrayCheap for () {61 fn len(&self) -> u32 {62 063 }64 fn get(&self, _index: u32) -> Option<Val> {65 None66 }67}6869#[derive(Debug, Trace)]70pub struct SliceArray {71 pub(crate) inner: ArrValue,72 pub(crate) from: u32,73 pub(crate) to: u32,74 pub(crate) step: u32,75}7677impl SliceArray {78 fn map_idx(&self, index: u32) -> u32 {79 self.from + self.step * index80 }81}82impl ArrayLike for SliceArray {83 fn len32(&self) -> u32 {84 (self.to - self.from).div_ceil(self.step)85 }8687 fn get32(&self, index: u32) -> Result<Option<Val>> {88 self.inner.get32(self.map_idx(index))89 }9091 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {92 self.inner.get_lazy32(self.map_idx(index))93 }9495 fn is_cheap(&self) -> bool {96 self.inner.is_cheap()97 }98}99100impl ArrayCheap for IBytes {101 fn len(&self) -> u32 {102 arridx(self.as_slice().len())103 }104 fn get(&self, index: u32) -> Option<Val> {105 self.as_slice()106 .get(index as usize)107 .map(|v| Val::Num((*v).into()))108 }109}110111#[derive(Debug, Trace, Clone)]112enum ArrayThunk {113 Computed(Val),114 Errored(Error),115 Waiting,116 Pending,117}118119#[derive(Debug, Trace, Clone)]120pub struct ExprArray {121 ctx: Context,122 src: Rc<Vec<LExpr>>,123 cached: Cc<RefCell<Vec<ArrayThunk>>>,124}125impl ExprArray {126 pub fn new(outer: Context, shape: &ClosureShape, src: Rc<Vec<LExpr>>) -> Self {127 Self {128 ctx: Context::enter_using(&outer, shape),129 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),130 src,131 }132 }133}134impl ArrayLike for ExprArray {135 fn len32(&self) -> u32 {136 arridx(self.cached.borrow().len())137 }138 fn get32(&self, index: u32) -> Result<Option<Val>> {139 if index >= self.len32() {140 return Ok(None);141 }142 match &self.cached.borrow()[index as usize] {143 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),144 ArrayThunk::Errored(e) => return Err(e.clone()),145 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),146 ArrayThunk::Waiting => {}147 }148149 let ArrayThunk::Waiting = replace(150 &mut self.cached.borrow_mut()[index as usize],151 ArrayThunk::Pending,152 ) else {153 unreachable!()154 };155156 let new_value: Val = evaluate(self.ctx.clone(), &self.src[index as usize])?;157 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());158 Ok(Some(new_value))159 }160 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {161 #[derive(Trace)]162 struct ExprArrThunk {163 expr: ExprArray,164 index: u32,165 }166 impl ThunkValue for ExprArrThunk {167 type Output = Val;168169 fn get(&self) -> Result<Self::Output> {170 self.expr171 .get32(self.index)172 .transpose()173 .expect("index checked")174 }175 }176177 if index >= self.len32() {178 return None;179 }180 match &self.cached.borrow()[index as usize] {181 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),182 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),183 ArrayThunk::Waiting | ArrayThunk::Pending => {}184 }185186 Some(Thunk::new(ExprArrThunk {187 expr: self.clone(),188 index,189 }))190 }191 fn is_cheap(&self) -> bool {192 false193 }194}195196#[derive(Trace, Debug)]197pub struct ExtendedArray {198 pub a: ArrValue,199 pub b: ArrValue,200 split: u32,201 len: u32,202}203impl ExtendedArray {204 pub fn new(a: ArrValue, b: ArrValue) -> Option<Self> {205 let a_len = a.len32();206 let b_len = b.len32();207 let len = a_len.checked_add(b_len)?;208 Some(Self {209 a,210 b,211 split: a_len,212 len,213 })214 }215}216217struct WithExactSize<I>(I, usize);218impl<I, T> Iterator for WithExactSize<I>219where220 I: Iterator<Item = T>,221{222 type Item = T;223224 fn next(&mut self) -> Option<Self::Item> {225 self.0.next()226 }227 fn nth(&mut self, n: usize) -> Option<Self::Item> {228 self.0.nth(n)229 }230 fn size_hint(&self) -> (usize, Option<usize>) {231 (self.1, Some(self.1))232 }233}234impl<I> DoubleEndedIterator for WithExactSize<I>235where236 I: DoubleEndedIterator,237{238 fn next_back(&mut self) -> Option<Self::Item> {239 self.0.next_back()240 }241 fn nth_back(&mut self, n: usize) -> Option<Self::Item> {242 self.0.nth_back(n)243 }244}245impl<I> ExactSizeIterator for WithExactSize<I>246where247 I: Iterator,248{249 fn len(&self) -> usize {250 self.1251 }252}253impl ArrayLike for ExtendedArray {254 fn get32(&self, index: u32) -> Result<Option<Val>> {255 if self.split > index {256 self.a.get32(index)257 } else {258 self.b.get32(index - self.split)259 }260 }261 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {262 if self.split > index {263 self.a.get_lazy32(index)264 } else {265 self.b.get_lazy32(index - self.split)266 }267 }268269 fn len32(&self) -> u32 {270 self.len271 }272273 fn is_cheap(&self) -> bool {274 self.a.is_cheap() && self.b.is_cheap()275 }276}277278impl<T> ArrayLike for Vec<T>279where280 T: IntoUntyped + Trace + fmt::Debug,281 for<'a> &'a T: IntoUntyped,282{283 fn len32(&self) -> u32 {284 self.as_slice().len().try_into().unwrap_or(u32::MAX)285 }286287 fn get32(&self, index: u32) -> Result<Option<Val>> {288 let Some(elem) = self.as_slice().get(index as usize) else {289 return Ok(None);290 };291 IntoUntyped::into_untyped(elem).map(Some)292 }293294 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {295 let elem = self.as_slice().get(index as usize)?;296 Some(IntoUntyped::into_lazy_untyped(elem))297 }298299 fn is_cheap(&self) -> bool {300 !T::provides_lazy()301 }302}303304/// Inclusive range type305#[derive(Debug, Trace, PartialEq, Eq)]306pub struct RangeArray {307 start: i32,308 end: i32,309}310impl RangeArray {311 pub fn empty() -> Self {312 Self::new_exclusive(0, 0)313 }314 pub fn new_exclusive(start: i32, end: i32) -> Self {315 end.checked_sub(1)316 .map_or_else(Self::empty, |end| Self { start, end })317 }318 pub fn new_inclusive(start: i32, end: i32) -> Self {319 Self { start, end }320 }321 #[expect(322 clippy::cast_sign_loss,323 reason = "the math is valid with wrapping, sign loss works as intended"324 )]325 fn size(&self) -> u32 {326 (self.end as u32)327 .wrapping_sub(self.start as u32)328 .wrapping_add(1)329 }330 fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {331 WithExactSize(self.start..=self.end, self.size() as usize)332 }333}334impl ArrayCheap for RangeArray {335 fn get(&self, index: u32) -> Option<Val> {336 self.range().nth(index as usize).map(|i| Val::Num(i.into()))337 }338 fn len(&self) -> u32 {339 self.size()340 }341}342343#[derive(Debug, Trace)]344pub struct ReverseArray(pub ArrValue);345impl ArrayLike for ReverseArray {346 fn len32(&self) -> u32 {347 self.0.len32()348 }349350 fn get32(&self, index: u32) -> Result<Option<Val>> {351 self.0.get32(self.0.len32() - index - 1)352 }353354 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {355 self.0.get_lazy32(self.0.len32() - index - 1)356 }357358 fn is_cheap(&self) -> bool {359 self.0.is_cheap()360 }361}362363#[derive(Trace, Clone, Debug)]364pub enum ArrayMapper {365 Plain(NativeFn!((Val) -> Val)),366 WithIndex(NativeFn!((u32, Val) -> Val)),367}368369#[derive(Trace, Debug, Clone)]370pub struct MappedArray {371 inner: ArrValue,372 cached: Cc<RefCell<Vec<ArrayThunk>>>,373 mapper: ArrayMapper,374}375impl MappedArray {376 pub fn new(inner: ArrValue, mapper: ArrayMapper) -> Self {377 let len = inner.len32();378 Self {379 inner,380 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),381 mapper,382 }383 }384 fn evaluate(&self, index: u32, value: Val) -> Result<Val> {385 match &self.mapper {386 ArrayMapper::Plain(f) => f.call(value),387 ArrayMapper::WithIndex(f) => f.call(index, value),388 }389 }390}391impl ArrayLike for MappedArray {392 fn len32(&self) -> u32 {393 arridx(self.cached.borrow().len())394 }395396 fn get32(&self, index: u32) -> Result<Option<Val>> {397 if index >= self.len32() {398 return Ok(None);399 }400 match &self.cached.borrow()[index as usize] {401 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),402 ArrayThunk::Errored(e) => return Err(e.clone()),403 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),404 ArrayThunk::Waiting => {}405 }406407 let ArrayThunk::Waiting = replace(408 &mut self.cached.borrow_mut()[index as usize],409 ArrayThunk::Pending,410 ) else {411 unreachable!()412 };413414 let val = self415 .inner416 .get32(index)417 .transpose()418 .expect("index checked")419 .and_then(|r| self.evaluate(index, r));420421 let new_value = match val {422 Ok(v) => v,423 Err(e) => {424 self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());425 return Err(e);426 }427 };428 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());429 Ok(Some(new_value))430 }431 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {432 #[derive(Trace)]433 struct MappedArrayThunk {434 arr: MappedArray,435 index: u32,436 }437 impl ThunkValue for MappedArrayThunk {438 type Output = Val;439440 fn get(&self) -> Result<Self::Output> {441 self.arr442 .get32(self.index)443 .transpose()444 .expect("index checked")445 }446 }447448 if index >= self.len32() {449 return None;450 }451 match &self.cached.borrow()[index as usize] {452 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),453 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),454 ArrayThunk::Waiting | ArrayThunk::Pending => {}455 }456457 Some(Thunk::new(MappedArrayThunk {458 arr: self.clone(),459 index,460 }))461 }462}463#[derive(Trace, Debug, Clone)]464pub struct MakeArray {465 cached: Cc<RefCell<Vec<ArrayThunk>>>,466 mapper: NativeFn!((u32,)->Val),467}468impl MakeArray {469 pub fn new(len: u32, mapper: NativeFn!((u32)->Val)) -> Self {470 Self {471 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),472 mapper,473 }474 }475}476impl ArrayLike for MakeArray {477 fn len32(&self) -> u32 {478 arridx(self.cached.borrow().len())479 }480481 fn get32(&self, index: u32) -> Result<Option<Val>> {482 if index >= self.len32() {483 return Ok(None);484 }485 match &self.cached.borrow()[index as usize] {486 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),487 ArrayThunk::Errored(e) => return Err(e.clone()),488 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),489 ArrayThunk::Waiting => {}490 }491492 let ArrayThunk::Waiting = replace(493 &mut self.cached.borrow_mut()[index as usize],494 ArrayThunk::Pending,495 ) else {496 unreachable!()497 };498499 let val = self.mapper.call(index);500501 let new_value = match val {502 Ok(v) => v,503 Err(e) => {504 self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());505 return Err(e);506 }507 };508 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());509 Ok(Some(new_value))510 }511 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {512 #[derive(Trace)]513 struct MakeArrayThunk {514 arr: MakeArray,515 index: u32,516 }517 impl ThunkValue for MakeArrayThunk {518 type Output = Val;519520 fn get(&self) -> Result<Self::Output> {521 self.arr522 .get32(self.index)523 .transpose()524 .expect("index checked")525 }526 }527528 if index >= self.len32() {529 return None;530 }531 match &self.cached.borrow()[index as usize] {532 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),533 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),534 ArrayThunk::Waiting | ArrayThunk::Pending => {}535 }536537 Some(Thunk::new(MakeArrayThunk {538 arr: self.clone(),539 index,540 }))541 }542}543544#[derive(Trace, Debug)]545pub struct RepeatedArray {546 data: ArrValue,547 repeats: u32,548 total_len: u32,549}550impl RepeatedArray {551 pub fn new(data: ArrValue, repeats: u32) -> Option<Self> {552 let total_len = data.len32().checked_mul(repeats)?;553 Some(Self {554 data,555 repeats,556 total_len,557 })558 }559 fn map_idx(&self, index: u32) -> Option<u32> {560 if index > self.total_len {561 return None;562 }563 Some(index % self.data.len32())564 }565}566567impl ArrayLike for RepeatedArray {568 fn len32(&self) -> u32 {569 self.total_len570 }571572 fn get32(&self, index: u32) -> Result<Option<Val>> {573 let Some(idx) = self.map_idx(index) else {574 return Ok(None);575 };576 self.data.get32(idx)577 }578579 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {580 let idx = self.map_idx(index)?;581 self.data.get_lazy32(idx)582 }583584 fn is_cheap(&self) -> bool {585 self.data.is_cheap()586 }587}588589#[derive(Trace, Debug)]590pub struct PickObjectValues {591 obj: ObjValue,592 keys: Vec<IStr>,593}594595impl PickObjectValues {596 pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {597 Self { obj, keys }598 }599}600601impl ArrayLike for PickObjectValues {602 fn len32(&self) -> u32 {603 arridx(self.keys.len())604 }605606 fn get32(&self, index: u32) -> Result<Option<Val>> {607 let Some(key) = self.keys.as_slice().get(index as usize) else {608 return Ok(None);609 };610 Ok(Some(self.obj.get_or_bail(key.clone())?))611 }612613 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {614 let key = self.keys.as_slice().get(index as usize)?;615 Some(self.obj.get_lazy_or_bail(key.clone()))616 }617618 fn is_cheap(&self) -> bool {619 false620 }621}622623#[derive(Trace, Debug)]624pub struct PickObjectKeyValues {625 obj: ObjValue,626 keys: Vec<IStr>,627}628629impl PickObjectKeyValues {630 pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {631 Self { obj, keys }632 }633}634635#[derive(Typed, IntoUntyped)]636pub struct KeyValue {637 key: IStr,638 value: Thunk<Val>,639}640641impl ArrayLike for PickObjectKeyValues {642 fn len32(&self) -> u32 {643 arridx(self.keys.len())644 }645646 fn get32(&self, index: u32) -> Result<Option<Val>> {647 let Some(key) = self.keys.as_slice().get(index as usize) else {648 return Ok(None);649 };650 Ok(Some(651 KeyValue::into_untyped(KeyValue {652 key: key.clone(),653 value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),654 })655 .expect("convertible"),656 ))657 }658659 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {660 let key = self.keys.as_slice().get(index as usize)?;661 // Nothing can fail in the key part, yet value is still662 // lazy-evaluated663 Some(Thunk::evaluated(664 KeyValue::into_untyped(KeyValue {665 key: key.clone(),666 value: self.obj.get_lazy_or_bail(key.clone()),667 })668 .expect("convertible"),669 ))670 }671672 fn is_cheap(&self) -> bool {673 false674 }675}1use std::{2 any::Any,3 cell::RefCell,4 fmt::{self, Debug},5 mem::replace,6 rc::Rc,7};89use jrsonnet_gcmodule::{Cc, Trace};10use jrsonnet_interner::{IBytes, IStr};11use jrsonnet_ir::TrivialVal;1213use super::{ArrValue, arridx};14use crate::{15 Context, Error, ObjValue, Result, Thunk, Val,16 analyze::LExpr,17 error::ErrorKind::InfiniteRecursionDetected,18 evaluate::evaluate,19 function::NativeFn,20 typed::{IntoUntyped, Typed},21 val::ThunkValue,22};2324pub trait ArrayLike: Any + Trace + Debug {25 fn len32(&self) -> u32;26 fn is_empty(&self) -> bool {27 self.len32() == 028 }29 fn get32(&self, index: u32) -> Result<Option<Val>>;30 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>>;3132 fn is_cheap(&self) -> bool {33 false34 }35}36trait ArrayCheap {37 fn get(&self, index: u32) -> Option<Val>;38 fn len(&self) -> u32;39}40impl<T> ArrayLike for T41where42 T: Any + Trace + Debug + ArrayCheap,43{44 fn len32(&self) -> u32 {45 <T as ArrayCheap>::len(self)46 }4748 fn get32(&self, index: u32) -> Result<Option<Val>> {49 Ok(<T as ArrayCheap>::get(self, index))50 }5152 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {53 <T as ArrayCheap>::get(self, index).map(Thunk::evaluated)54 }5556 fn is_cheap(&self) -> bool {57 true58 }59}6061impl ArrayCheap for () {62 fn len(&self) -> u32 {63 064 }65 fn get(&self, _index: u32) -> Option<Val> {66 None67 }68}6970#[derive(Debug, Trace)]71pub struct SliceArray {72 pub(crate) inner: ArrValue,73 pub(crate) from: u32,74 pub(crate) to: u32,75 pub(crate) step: u32,76}7778impl SliceArray {79 fn map_idx(&self, index: u32) -> u32 {80 self.from + self.step * index81 }82}83impl ArrayLike for SliceArray {84 fn len32(&self) -> u32 {85 (self.to - self.from).div_ceil(self.step)86 }8788 fn get32(&self, index: u32) -> Result<Option<Val>> {89 self.inner.get32(self.map_idx(index))90 }9192 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {93 self.inner.get_lazy32(self.map_idx(index))94 }9596 fn is_cheap(&self) -> bool {97 self.inner.is_cheap()98 }99}100101impl ArrayCheap for IBytes {102 fn len(&self) -> u32 {103 arridx(self.as_slice().len())104 }105 fn get(&self, index: u32) -> Option<Val> {106 self.as_slice()107 .get(index as usize)108 .map(|v| Val::Num((*v).into()))109 }110}111112impl ArrayCheap for Rc<Vec<TrivialVal>> {113 fn get(&self, index: u32) -> Option<Val> {114 self.as_slice()115 .get(index as usize)116 .map(|tv| tv.clone().into())117 }118119 fn len(&self) -> u32 {120 arridx(self.as_slice().len())121 }122}123124#[derive(Debug, Trace, Clone)]125enum ArrayThunk {126 Computed(Val),127 Errored(Error),128 Waiting,129 Pending,130}131132#[derive(Debug, Trace, Clone)]133pub struct ExprArray {134 ctx: Context,135 src: Rc<Vec<LExpr>>,136 cached: Cc<RefCell<Vec<ArrayThunk>>>,137}138impl ExprArray {139 pub fn new(ctx: Context, src: Rc<Vec<LExpr>>) -> Self {140 Self {141 ctx,142 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),143 src,144 }145 }146}147impl ArrayLike for ExprArray {148 fn len32(&self) -> u32 {149 arridx(self.cached.borrow().len())150 }151 fn get32(&self, index: u32) -> Result<Option<Val>> {152 if index >= self.len32() {153 return Ok(None);154 }155 match &self.cached.borrow()[index as usize] {156 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),157 ArrayThunk::Errored(e) => return Err(e.clone()),158 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),159 ArrayThunk::Waiting => {}160 }161162 let ArrayThunk::Waiting = replace(163 &mut self.cached.borrow_mut()[index as usize],164 ArrayThunk::Pending,165 ) else {166 unreachable!()167 };168169 let result = evaluate(self.ctx.clone(), &self.src[index as usize]);170 match result {171 Ok(new_value) => {172 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());173 Ok(Some(new_value))174 }175 Err(e) => {176 self.cached.borrow_mut()[index as usize] = ArrayThunk::Waiting;177 Err(e)178 }179 }180 }181 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {182 #[derive(Trace)]183 struct ExprArrThunk {184 expr: ExprArray,185 index: u32,186 }187 impl ThunkValue for ExprArrThunk {188 type Output = Val;189190 fn get(&self) -> Result<Self::Output> {191 self.expr192 .get32(self.index)193 .transpose()194 .expect("index checked")195 }196 }197198 if index >= self.len32() {199 return None;200 }201 match &self.cached.borrow()[index as usize] {202 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),203 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),204 ArrayThunk::Waiting | ArrayThunk::Pending => {}205 }206207 Some(Thunk::new(ExprArrThunk {208 expr: self.clone(),209 index,210 }))211 }212 fn is_cheap(&self) -> bool {213 false214 }215}216217#[derive(Trace, Debug)]218pub struct ExtendedArray {219 pub a: ArrValue,220 pub b: ArrValue,221 split: u32,222 len: u32,223}224impl ExtendedArray {225 pub fn new(a: ArrValue, b: ArrValue) -> Option<Self> {226 let a_len = a.len32();227 let b_len = b.len32();228 let len = a_len.checked_add(b_len)?;229 Some(Self {230 a,231 b,232 split: a_len,233 len,234 })235 }236}237238struct WithExactSize<I>(I, usize);239impl<I, T> Iterator for WithExactSize<I>240where241 I: Iterator<Item = T>,242{243 type Item = T;244245 fn next(&mut self) -> Option<Self::Item> {246 self.0.next()247 }248 fn nth(&mut self, n: usize) -> Option<Self::Item> {249 self.0.nth(n)250 }251 fn size_hint(&self) -> (usize, Option<usize>) {252 (self.1, Some(self.1))253 }254}255impl<I> DoubleEndedIterator for WithExactSize<I>256where257 I: DoubleEndedIterator,258{259 fn next_back(&mut self) -> Option<Self::Item> {260 self.0.next_back()261 }262 fn nth_back(&mut self, n: usize) -> Option<Self::Item> {263 self.0.nth_back(n)264 }265}266impl<I> ExactSizeIterator for WithExactSize<I>267where268 I: Iterator,269{270 fn len(&self) -> usize {271 self.1272 }273}274impl ArrayLike for ExtendedArray {275 fn get32(&self, index: u32) -> Result<Option<Val>> {276 if self.split > index {277 self.a.get32(index)278 } else {279 self.b.get32(index - self.split)280 }281 }282 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {283 if self.split > index {284 self.a.get_lazy32(index)285 } else {286 self.b.get_lazy32(index - self.split)287 }288 }289290 fn len32(&self) -> u32 {291 self.len292 }293294 fn is_cheap(&self) -> bool {295 self.a.is_cheap() && self.b.is_cheap()296 }297}298299impl<T> ArrayLike for Vec<T>300where301 T: IntoUntyped + Trace + fmt::Debug,302 for<'a> &'a T: IntoUntyped,303{304 fn len32(&self) -> u32 {305 self.as_slice().len().try_into().unwrap_or(u32::MAX)306 }307308 fn get32(&self, index: u32) -> Result<Option<Val>> {309 let Some(elem) = self.as_slice().get(index as usize) else {310 return Ok(None);311 };312 IntoUntyped::into_untyped(elem).map(Some)313 }314315 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {316 let elem = self.as_slice().get(index as usize)?;317 Some(IntoUntyped::into_lazy_untyped(elem))318 }319320 fn is_cheap(&self) -> bool {321 !T::provides_lazy()322 }323}324325/// Inclusive range type326#[derive(Debug, Trace, PartialEq, Eq)]327pub struct RangeArray {328 start: i32,329 end: i32,330}331impl RangeArray {332 pub fn empty() -> Self {333 Self::new_exclusive(0, 0)334 }335 pub fn new_exclusive(start: i32, end: i32) -> Self {336 end.checked_sub(1)337 .map_or_else(Self::empty, |end| Self { start, end })338 }339 pub fn new_inclusive(start: i32, end: i32) -> Self {340 Self { start, end }341 }342 #[expect(343 clippy::cast_sign_loss,344 reason = "the math is valid with wrapping, sign loss works as intended"345 )]346 fn size(&self) -> u32 {347 (self.end as u32)348 .wrapping_sub(self.start as u32)349 .wrapping_add(1)350 }351 fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {352 WithExactSize(self.start..=self.end, self.size() as usize)353 }354}355impl ArrayCheap for RangeArray {356 fn get(&self, index: u32) -> Option<Val> {357 self.range().nth(index as usize).map(|i| Val::Num(i.into()))358 }359 fn len(&self) -> u32 {360 self.size()361 }362}363364#[derive(Debug, Trace)]365pub struct ReverseArray(pub ArrValue);366impl ArrayLike for ReverseArray {367 fn len32(&self) -> u32 {368 self.0.len32()369 }370371 fn get32(&self, index: u32) -> Result<Option<Val>> {372 self.0.get32(self.0.len32() - index - 1)373 }374375 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {376 self.0.get_lazy32(self.0.len32() - index - 1)377 }378379 fn is_cheap(&self) -> bool {380 self.0.is_cheap()381 }382}383384#[derive(Trace, Clone, Debug)]385pub enum ArrayMapper {386 Plain(NativeFn!((Val) -> Val)),387 WithIndex(NativeFn!((u32, Val) -> Val)),388}389390#[derive(Trace, Debug, Clone)]391pub struct MappedArray {392 inner: ArrValue,393 cached: Cc<RefCell<Vec<ArrayThunk>>>,394 mapper: ArrayMapper,395}396impl MappedArray {397 pub fn new(inner: ArrValue, mapper: ArrayMapper) -> Self {398 let len = inner.len32();399 Self {400 inner,401 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),402 mapper,403 }404 }405 fn evaluate(&self, index: u32, value: Val) -> Result<Val> {406 match &self.mapper {407 ArrayMapper::Plain(f) => f.call(value),408 ArrayMapper::WithIndex(f) => f.call(index, value),409 }410 }411}412impl ArrayLike for MappedArray {413 fn len32(&self) -> u32 {414 arridx(self.cached.borrow().len())415 }416417 fn get32(&self, index: u32) -> Result<Option<Val>> {418 if index >= self.len32() {419 return Ok(None);420 }421 match &self.cached.borrow()[index as usize] {422 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),423 ArrayThunk::Errored(e) => return Err(e.clone()),424 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),425 ArrayThunk::Waiting => {}426 }427428 let ArrayThunk::Waiting = replace(429 &mut self.cached.borrow_mut()[index as usize],430 ArrayThunk::Pending,431 ) else {432 unreachable!()433 };434435 let val = self436 .inner437 .get32(index)438 .transpose()439 .expect("index checked")440 .and_then(|r| self.evaluate(index, r));441442 let new_value = match val {443 Ok(v) => v,444 Err(e) => {445 self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());446 return Err(e);447 }448 };449 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());450 Ok(Some(new_value))451 }452 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {453 #[derive(Trace)]454 struct MappedArrayThunk {455 arr: MappedArray,456 index: u32,457 }458 impl ThunkValue for MappedArrayThunk {459 type Output = Val;460461 fn get(&self) -> Result<Self::Output> {462 self.arr463 .get32(self.index)464 .transpose()465 .expect("index checked")466 }467 }468469 if index >= self.len32() {470 return None;471 }472 match &self.cached.borrow()[index as usize] {473 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),474 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),475 ArrayThunk::Waiting | ArrayThunk::Pending => {}476 }477478 Some(Thunk::new(MappedArrayThunk {479 arr: self.clone(),480 index,481 }))482 }483}484#[derive(Trace, Debug, Clone)]485pub struct MakeArray {486 cached: Cc<RefCell<Vec<ArrayThunk>>>,487 mapper: NativeFn!((u32,)->Val),488}489impl MakeArray {490 pub fn new(len: u32, mapper: NativeFn!((u32)->Val)) -> Self {491 Self {492 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),493 mapper,494 }495 }496}497impl ArrayLike for MakeArray {498 fn len32(&self) -> u32 {499 arridx(self.cached.borrow().len())500 }501502 fn get32(&self, index: u32) -> Result<Option<Val>> {503 if index >= self.len32() {504 return Ok(None);505 }506 match &self.cached.borrow()[index as usize] {507 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),508 ArrayThunk::Errored(e) => return Err(e.clone()),509 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),510 ArrayThunk::Waiting => {}511 }512513 let ArrayThunk::Waiting = replace(514 &mut self.cached.borrow_mut()[index as usize],515 ArrayThunk::Pending,516 ) else {517 unreachable!()518 };519520 let val = self.mapper.call(index);521522 let new_value = match val {523 Ok(v) => v,524 Err(e) => {525 self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());526 return Err(e);527 }528 };529 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());530 Ok(Some(new_value))531 }532 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {533 #[derive(Trace)]534 struct MakeArrayThunk {535 arr: MakeArray,536 index: u32,537 }538 impl ThunkValue for MakeArrayThunk {539 type Output = Val;540541 fn get(&self) -> Result<Self::Output> {542 self.arr543 .get32(self.index)544 .transpose()545 .expect("index checked")546 }547 }548549 if index >= self.len32() {550 return None;551 }552 match &self.cached.borrow()[index as usize] {553 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),554 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),555 ArrayThunk::Waiting | ArrayThunk::Pending => {}556 }557558 Some(Thunk::new(MakeArrayThunk {559 arr: self.clone(),560 index,561 }))562 }563}564565#[derive(Trace, Debug)]566pub struct RepeatedArray {567 data: ArrValue,568 repeats: u32,569 total_len: u32,570}571impl RepeatedArray {572 pub fn new(data: ArrValue, repeats: u32) -> Option<Self> {573 let total_len = data.len32().checked_mul(repeats)?;574 Some(Self {575 data,576 repeats,577 total_len,578 })579 }580 fn map_idx(&self, index: u32) -> Option<u32> {581 if index > self.total_len {582 return None;583 }584 Some(index % self.data.len32())585 }586}587588impl ArrayLike for RepeatedArray {589 fn len32(&self) -> u32 {590 self.total_len591 }592593 fn get32(&self, index: u32) -> Result<Option<Val>> {594 let Some(idx) = self.map_idx(index) else {595 return Ok(None);596 };597 self.data.get32(idx)598 }599600 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {601 let idx = self.map_idx(index)?;602 self.data.get_lazy32(idx)603 }604605 fn is_cheap(&self) -> bool {606 self.data.is_cheap()607 }608}609610#[derive(Trace, Debug)]611pub struct PickObjectValues {612 obj: ObjValue,613 keys: Vec<IStr>,614}615616impl PickObjectValues {617 pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {618 Self { obj, keys }619 }620}621622impl ArrayLike for PickObjectValues {623 fn len32(&self) -> u32 {624 arridx(self.keys.len())625 }626627 fn get32(&self, index: u32) -> Result<Option<Val>> {628 let Some(key) = self.keys.as_slice().get(index as usize) else {629 return Ok(None);630 };631 Ok(Some(self.obj.get_or_bail(key.clone())?))632 }633634 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {635 let key = self.keys.as_slice().get(index as usize)?;636 Some(self.obj.get_lazy_or_bail(key.clone()))637 }638639 fn is_cheap(&self) -> bool {640 false641 }642}643644#[derive(Trace, Debug)]645pub struct PickObjectKeyValues {646 obj: ObjValue,647 keys: Vec<IStr>,648}649650impl PickObjectKeyValues {651 pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {652 Self { obj, keys }653 }654}655656#[derive(Typed, IntoUntyped)]657pub struct KeyValue {658 key: IStr,659 value: Thunk<Val>,660}661662impl ArrayLike for PickObjectKeyValues {663 fn len32(&self) -> u32 {664 arridx(self.keys.len())665 }666667 fn get32(&self, index: u32) -> Result<Option<Val>> {668 let Some(key) = self.keys.as_slice().get(index as usize) else {669 return Ok(None);670 };671 Ok(Some(672 KeyValue::into_untyped(KeyValue {673 key: key.clone(),674 value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),675 })676 .expect("convertible"),677 ))678 }679680 fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {681 let key = self.keys.as_slice().get(index as usize)?;682 // Nothing can fail in the key part, yet value is still683 // lazy-evaluated684 Some(Thunk::evaluated(685 KeyValue::into_untyped(KeyValue {686 key: key.clone(),687 value: self.obj.get_lazy_or_bail(key.clone()),688 })689 .expect("convertible"),690 ))691 }692693 fn is_cheap(&self) -> bool {694 false695 }696}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(_))