difftreelog
feat minimal tco
in: master
3 files changed
crates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -4,9 +4,7 @@
use crate::{
Context, LocalsFrame, PackedContext, Result, SupThis, Thunk, Unbound, Val,
- analyze::{
- ClosureShape, LBind, LDestruct, LDestructField, LDestructRest, LLocalExpr, LocalSlot,
- },
+ analyze::{ClosureShape, LBind, LDestruct, LDestructField, LDestructRest, LocalSlot},
bail,
evaluate::evaluate,
};
@@ -187,15 +185,6 @@
ctx,
);
}
-}
-
-pub fn evaluate_local_expr(parent: Context, l: &LLocalExpr) -> Result<Val> {
- let ctx = parent
- .pack_captures_sup_this(&l.frame_shape)
- .enter(|fill, ctx| {
- fill_letrec_binds(fill, ctx, &l.binds);
- });
- evaluate(ctx, &l.body)
}
pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -7,7 +7,7 @@
use self::{
compspec::{evaluate_arr_comp, evaluate_obj_comp},
- destructure::{evaluate_local_expr, evaluate_locals_unbound},
+ destructure::evaluate_locals_unbound,
operator::evaluate_binary_op_special,
};
use crate::{
@@ -116,129 +116,143 @@
}
#[allow(clippy::too_many_lines)]
-pub fn evaluate(ctx: Context, expr: &LExpr) -> Result<Val> {
- 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::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::UnaryOp(op, value) => {
- let value = evaluate(ctx, value)?;
- evaluate_unary_op(*op, &value)?
- }
- LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,
- LExpr::LocalExpr(local_expr) => evaluate_local_expr(ctx, local_expr)?,
- LExpr::IfElse {
- cond,
- cond_then,
- cond_else,
- } => {
- let cond_val = evaluate(ctx.clone(), cond)?;
- let Val::Bool(b) = cond_val else {
- bail!(TypeMismatch(
- "if condition",
- vec![ValType::Bool],
- cond_val.value_type()
- ))
- };
- if b {
- evaluate(ctx, cond_then)?
- } else if let Some(e) = cond_else {
- evaluate(ctx, e)?
- } else {
+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::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::UnaryOp(op, value) => {
+ let value = evaluate(ctx, value)?;
+ evaluate_unary_op(*op, &value)?
+ }
+ LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,
+ LExpr::LocalExpr(l) => {
+ ctx = ctx
+ .pack_captures_sup_this(&l.frame_shape)
+ .enter(|fill, ctx| {
+ fill_letrec_binds(fill, ctx, &l.binds);
+ });
+ expr = &l.body;
+ continue;
+ }
+ LExpr::IfElse {
+ cond,
+ cond_then,
+ cond_else,
+ } => {
+ let cond_val = evaluate(ctx.clone(), cond)?;
+ let Val::Bool(b) = cond_val else {
+ bail!(TypeMismatch(
+ "if condition",
+ vec![ValType::Bool],
+ cond_val.value_type()
+ ))
+ };
+ if b {
+ expr = cond_then;
+ continue;
+ } else if let Some(e) = cond_else {
+ expr = e;
+ continue;
+ }
Val::Null
}
- }
- LExpr::Error(s, e) => in_frame(
- CallLocation::new(s),
- || "error statement".to_owned(),
- || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),
- )?,
- LExpr::AssertExpr { assert, rest } => {
- evaluate_assert(ctx.clone(), assert)?;
- evaluate(ctx, rest)?
- }
+ LExpr::Error(s, e) => in_frame(
+ CallLocation::new(s),
+ || "error statement".to_owned(),
+ || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),
+ )?,
+ LExpr::AssertExpr { assert, rest } => {
+ evaluate_assert(ctx.clone(), assert)?;
+ expr = rest;
+ continue;
+ }
- LExpr::Function(func) => evaluate_method(
- ctx,
- func.name.clone().unwrap_or_else(names::anonymous),
- func,
- ),
- LExpr::IdentityFunction => Val::Func(FuncVal::identity()),
- LExpr::Apply {
- applicable,
- args,
- tailstrict,
- } => evaluate_apply(
- ctx,
- applicable,
- args,
- CallLocation::new(&args.span),
- *tailstrict,
- )?,
- LExpr::Index { indexable, parts } => evaluate_index(ctx, indexable, parts)?,
- LExpr::Obj(body) => evaluate_obj_body(None, ctx, body)?,
- LExpr::ObjExtend(lhs, body) => {
- let lhs_val = evaluate(ctx.clone(), lhs)?;
- let Val::Obj(lhs_obj) = lhs_val else {
- bail!(TypeMismatch(
- "object extend lhs",
- vec![ValType::Obj],
- lhs_val.value_type(),
- ))
- };
- evaluate_obj_body(Some(lhs_obj), ctx, body)?
- }
- LExpr::ArrComp(comp) => evaluate_arr_comp(ctx, comp)?,
- LExpr::Slice(slice) => {
- let val = evaluate(ctx.clone(), &slice.value)?;
- let indexable = val.into_indexable()?;
- let start = slice
- .start
- .as_ref()
- .map(|e| evaluate(ctx.clone(), e))
- .transpose()?
- .map(|v| -> Result<i32> { i32::from_untyped(v).description("slice start value") })
- .transpose()?;
- let end = slice
- .end
- .as_ref()
- .map(|e| evaluate(ctx.clone(), e))
- .transpose()?
- .map(|v| -> Result<i32> { i32::from_untyped(v).description("slice end value") })
- .transpose()?;
- let step = slice
- .step
- .as_ref()
- .map(|e| evaluate(ctx, e))
- .transpose()?
- .map(|v| -> Result<BoundedUsize<1, { i32::MAX as usize }>> {
- BoundedUsize::from_untyped(v).description("slice step value")
+ LExpr::Function(func) => evaluate_method(
+ ctx,
+ func.name.clone().unwrap_or_else(names::anonymous),
+ func,
+ ),
+ LExpr::IdentityFunction => Val::Func(FuncVal::identity()),
+ LExpr::Apply {
+ applicable,
+ args,
+ tailstrict,
+ } => evaluate_apply(
+ ctx,
+ applicable,
+ args,
+ CallLocation::new(&args.span),
+ *tailstrict,
+ )?,
+ LExpr::Index { indexable, parts } => evaluate_index(ctx, indexable, parts)?,
+ LExpr::Obj(body) => evaluate_obj_body(None, ctx, body)?,
+ LExpr::ObjExtend(lhs, body) => {
+ let lhs_val = evaluate(ctx.clone(), lhs)?;
+ let Val::Obj(lhs_obj) = lhs_val else {
+ bail!(TypeMismatch(
+ "object extend lhs",
+ vec![ValType::Obj],
+ lhs_val.value_type(),
+ ))
+ };
+ evaluate_obj_body(Some(lhs_obj), ctx, body)?
+ }
+ LExpr::ArrComp(comp) => evaluate_arr_comp(ctx, comp)?,
+ LExpr::Slice(slice) => {
+ let val = evaluate(ctx.clone(), &slice.value)?;
+ let indexable = val.into_indexable()?;
+ let start = slice
+ .start
+ .as_ref()
+ .map(|e| evaluate(ctx.clone(), e))
+ .transpose()?
+ .map(|v| -> Result<i32> {
+ i32::from_untyped(v).description("slice start value")
+ })
+ .transpose()?;
+ let end = slice
+ .end
+ .as_ref()
+ .map(|e| evaluate(ctx.clone(), e))
+ .transpose()?
+ .map(|v| -> Result<i32> { i32::from_untyped(v).description("slice end value") })
+ .transpose()?;
+ let step = slice
+ .step
+ .as_ref()
+ .map(|e| evaluate(ctx, e))
+ .transpose()?
+ .map(|v| -> Result<BoundedUsize<1, { i32::MAX as usize }>> {
+ BoundedUsize::from_untyped(v).description("slice step value")
+ })
+ .transpose()?;
+ Val::from(indexable.slice32(start, end, step)?)
+ }
+ LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super().ok_or(NoSuperFound)?),
+ LExpr::Import {
+ kind,
+ kind_span,
+ path,
+ } => with_state(|state| {
+ let resolved = state.resolve_from(kind_span.0.source_path(), &path.clone())?;
+ Ok::<_, Error>(match kind.value {
+ ImportKind::Normal => in_frame(
+ CallLocation::new(&kind.span),
+ || "import".to_string(),
+ || state.import_resolved(resolved),
+ )?,
+ ImportKind::Str => Val::string(state.import_resolved_str(resolved)?),
+ ImportKind::Bin => Val::arr(state.import_resolved_bin(resolved)?),
})
- .transpose()?;
- Val::from(indexable.slice32(start, end, step)?)
- }
- LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super().ok_or(NoSuperFound)?),
- LExpr::Import {
- kind,
- kind_span,
- path,
- } => with_state(|state| {
- let resolved = state.resolve_from(kind_span.0.source_path(), &path.clone())?;
- Ok::<_, Error>(match kind.value {
- ImportKind::Normal => in_frame(
- CallLocation::new(&kind.span),
- || "import".to_string(),
- || state.import_resolved(resolved),
- )?,
- ImportKind::Str => Val::string(state.import_resolved_str(resolved)?),
- ImportKind::Bin => Val::arr(state.import_resolved_bin(resolved)?),
- })
- })?,
- })
+ })?,
+ });
+ }
}
fn evaluate_apply(
crates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth1use std::{fmt::Debug, rc::Rc};23use educe::Educe;4use jrsonnet_gcmodule::{Cc, Trace};5use jrsonnet_interner::IStr;6use jrsonnet_ir::Span;7pub use jrsonnet_macros::builtin;89use self::{10 builtin::Builtin,11 prepared::{PreparedCall, parse_prepared_builtin_call},12};13use crate::{14 Context, PackedContextSupThis, Result, Thunk, Val,15 analyze::LFunction,16 arr::arridx,17 evaluate::{destructure::destruct, ensure_sufficient_stack, evaluate, evaluate_trivial},18 function::builtin::BuiltinFunc,19};2021pub mod builtin;22mod native;23pub(crate) mod prepared;2425pub use jrsonnet_ir::function::*;26pub use native::NativeFn;27pub(crate) use prepared::PreparedFuncVal;2829/// Function callsite location.30/// Either from other jsonnet code, specified by expression location, or from native (without location).31#[derive(Clone, Copy)]32pub struct CallLocation<'l>(pub Option<&'l Span>);33impl<'l> CallLocation<'l> {34 /// Construct new location for calls coming from specified jsonnet expression location.35 pub const fn new(loc: &'l Span) -> Self {36 Self(Some(loc))37 }38}39impl CallLocation<'static> {40 /// Construct new location for calls coming from native code.41 pub const fn native() -> Self {42 Self(None)43 }44}4546/// Represents Jsonnet function defined in code.47#[derive(Trace, Educe)]48#[educe(Debug, PartialEq)]49pub struct FuncDesc {50 /// # Example51 ///52 /// In expressions like this, deducted to `a`, unspecified otherwise.53 /// ```jsonnet54 /// local a = function() ...55 /// local a() ...56 /// { a: function() ... }57 /// { a() = ... }58 /// ```59 pub name: IStr,60 pub(crate) body_captures: PackedContextSupThis,6162 #[educe(PartialEq(method = Rc::ptr_eq))]63 pub func: Rc<LFunction>,64}6566impl FuncDesc {67 pub fn signature(&self) -> FunctionSignature {68 self.func.signature.clone()69 }7071 pub fn call(72 &self,73 unnamed: &[Thunk<Val>],74 named: &[Thunk<Val>],75 prepared: &PreparedCall,76 ) -> Result<Val> {77 let body_ctx = self.body_captures.clone().enter(|fill, ctx| {78 // Place each provided arg-thunk into its destructured slots.79 for (param_idx, thunk) in unnamed.iter().enumerate() {80 destruct(81 &self.func.params[param_idx].destruct,82 fill,83 thunk.clone(),84 ctx,85 );86 }87 for &(param_idx, arg_idx) in prepared.named() {88 destruct(89 &self.func.params[param_idx].destruct,90 fill,91 named[arg_idx].clone(),92 ctx,93 );94 }9596 for ¶m_idx in prepared.defaults() {97 let param = &self.func.params[param_idx];98 let (shape, expr) = param.default.as_ref().expect("default exists");99 let expr = expr.clone();100 let env = Context::enter_using(ctx, shape);101102 destruct(103 ¶m.destruct,104 fill,105 Thunk!(move || evaluate(env, &expr)),106 ctx,107 );108 }109 });110111 ensure_sufficient_stack(|| evaluate(body_ctx, &self.func.body))112 }113114 pub fn evaluate_trivial(&self) -> Option<Val> {115 evaluate_trivial(&self.func.body)116 }117}118119/// Represents a Jsonnet function value, including plain functions and user-provided builtins.120#[allow(clippy::module_name_repetitions)]121#[derive(Trace, Clone)]122pub enum FuncVal {123 /// Plain function implemented in jsonnet.124 Normal(Cc<FuncDesc>),125 /// User-provided function.126 Builtin(BuiltinFunc),127}128129impl Debug for FuncVal {130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {131 match self {132 Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),133 Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),134 }135 }136}137138#[allow(clippy::unnecessary_wraps)]139#[builtin]140pub const fn builtin_id(x: Thunk<Val>) -> Thunk<Val> {141 x142}143144impl FuncVal {145 pub fn builtin(builtin: impl Builtin) -> Self {146 Self::Builtin(BuiltinFunc::new(builtin))147 }148149 pub fn identity() -> Self {150 Self::builtin(builtin_id {})151 }152153 pub fn params(&self) -> FunctionSignature {154 match self {155 Self::Builtin(i) => i.params(),156 Self::Normal(p) => p.signature(),157 }158 }159 /// Amount of non-default required arguments160 pub fn params_len32(&self) -> u32 {161 arridx(self.params().iter().filter(|p| !p.has_default()).count())162 }163 /// Function name, as defined in code.164 pub fn name(&self) -> IStr {165 match self {166 Self::Normal(normal) => normal.name.clone(),167 Self::Builtin(builtin) => builtin.name().into(),168 }169 }170171 pub(crate) fn evaluate_prepared(172 &self,173 prepared: &PreparedCall,174 loc: CallLocation<'_>,175 unnamed: &[Thunk<Val>],176 named: &[Thunk<Val>],177 _tailstrict: bool,178 ) -> Result<Val> {179 match self {180 FuncVal::Normal(func) => func.call(unnamed, named, prepared),181 FuncVal::Builtin(b) => {182 let args = parse_prepared_builtin_call(prepared, b.params(), unnamed, named);183 b.call(loc, &args)184 }185 }186 }187188 /// Is this function an identity function.189 ///190 /// This function should only be used for optimization, not for the conditional logic, i.e code should work with syntetic identity function too191 pub fn is_identity(&self) -> bool {192 match self {193 Self::Builtin(b) => b.as_any().downcast_ref::<builtin_id>().is_some(),194 Self::Normal(_) => false,195 }196 }197198 pub fn evaluate_trivial(&self) -> Option<Val> {199 match self {200 Self::Normal(n) => n.evaluate_trivial(),201 Self::Builtin(_) => None,202 }203 }204}205206impl<T> From<T> for FuncVal207where208 T: Builtin,209{210 fn from(value: T) -> Self {211 Self::builtin(value)212 }213}