--- 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>, }, + ArrConst(Rc>), ArrComp(Box), Obj(LObjBody), ObjExtend(Box, 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::>() + }); 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()), --- 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>) -> Self { - Self::new(ExprArray::new(ctx, shape, exprs)) + pub fn expr(ctx: Context, exprs: Rc>) -> Self { + Self::new(ExprArray::new(ctx, exprs)) } pub fn repeated(data: Self, repeats: u32) -> Option { --- 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> { + fn get(&self, index: u32) -> Option { + 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>>, } impl ExprArray { - pub fn new(outer: Context, shape: &ClosureShape, src: Rc>) -> Self { + pub fn new(ctx: Context, src: Rc>) -> 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> { #[derive(Trace)] --- 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 { - // 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) -> Val { @@ -119,13 +115,24 @@ pub fn evaluate(mut ctx: Context, mut expr: &LExpr) -> Result { 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 = 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)? --- 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 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(_))