git.delta.rocks / jrsonnet / refs/commits / be410fc52d08

difftreelog

refactor closure interpreter

yoxtmquyYaroslav Bolyukin2026-05-05parent: #644f9f3.patch.diff
in: master

14 files changed

modifiedcrates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth
99
10use crate::{Context, Result, Thunk, Val, analyze::LExpr, function::NativeFn, typed::IntoUntyped};10use crate::{
11 Context, Result, Thunk, Val,
12 analyze::{ClosureShape, LExpr},
13 function::NativeFn,
14 typed::IntoUntyped,
15};
1116
12mod spec;17mod spec;
36 Self::new(())41 Self::new(())
37 }42 }
3843
39 pub fn expr(ctx: Context, exprs: Rc<Vec<LExpr>>) -> Self {44 pub fn expr(ctx: Context, shape: &ClosureShape, exprs: Rc<Vec<LExpr>>) -> Self {
40 Self::new(ExprArray::new(ctx, exprs))45 Self::new(ExprArray::new(ctx, shape, exprs))
41 }46 }
4247
43 pub fn repeated(data: Self, repeats: u32) -> Option<Self> {48 pub fn repeated(data: Self, repeats: u32) -> Option<Self> {
modifiedcrates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth
12use super::ArrValue;12use super::ArrValue;
13use crate::{13use crate::{
14 Context, Error, ObjValue, Result, Thunk, Val,14 Context, Error, ObjValue, Result, Thunk, Val,
15 analyze::LExpr,15 analyze::{ClosureShape, LExpr},
16 error::ErrorKind::InfiniteRecursionDetected,16 error::ErrorKind::InfiniteRecursionDetected,
17 evaluate::evaluate,17 evaluate::evaluate,
18 function::NativeFn,18 function::NativeFn,
123 cached: Cc<RefCell<Vec<ArrayThunk>>>,123 cached: Cc<RefCell<Vec<ArrayThunk>>>,
124}124}
125impl ExprArray {125impl ExprArray {
126 pub fn new(ctx: Context, src: Rc<Vec<LExpr>>) -> Self {126 pub fn new(outer: Context, shape: &ClosureShape, src: Rc<Vec<LExpr>>) -> Self {
127 Self {127 Self {
128 ctx,128 ctx: Context::enter_using(&outer, shape),
129 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),129 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),
130 src,130 src,
131 }131 }
modifiedcrates/jrsonnet-evaluator/src/async_import.rsdiffbeforeafterboth
4use jrsonnet_ir::{IStr, Source, SourcePath, visit::Visitor};4use jrsonnet_ir::{IStr, Source, SourcePath, visit::Visitor};
5use rustc_hash::FxHashMap;5use rustc_hash::FxHashMap;
66
7use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, State};7use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, Result, State};
88
9pub struct Import {9pub struct Import {
10 path: ResolvePathOwned,10 path: ResolvePathOwned,
109 }109 }
110 }110 }
111 Job::ParseFile(path) => {111 Job::ParseFile(path) => {
112 if let Some(file) = s.0.file_cache.borrow_mut().get_mut(&path) {112 if let Some(file) = s.0.file_cache.borrow_mut().get_mut(&path)
113 if file.parsed.is_none() {113 && file.parsed.is_none()
114 {
114 let Some(code) = file.get_string() else {115 let Some(code) = file.get_string() else {
115 continue;116 continue;
128 }129 }
129 }130 }
130 }131 }
131 }
132 }132 }
133 Job::ResolveImport { from, import } => {133 Job::ResolveImport { from, import } => {
134 {134 {
modifiedcrates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
1use std::{clone::Clone, fmt::Debug};1use std::{
2 cell::{Cell, OnceCell, RefCell},
3 clone::Clone,
4 fmt::{self, Debug},
5};
26
3use educe::Educe;7use educe::Educe;
4use jrsonnet_gcmodule::{Cc, Trace};8use jrsonnet_gcmodule::{Cc, Trace};
5use jrsonnet_interner::IStr;9use jrsonnet_interner::IStr;
610
7use crate::{Pending, Result, SupThis, Thunk, Val, analyze::LocalId, error, error::ErrorKind::*};11use crate::{
12 Result, SupThis, Thunk, Val,
13 analyze::{CaptureSlot, ClosureShape, LSlot, LocalId, LocalSlot},
14 bail, error,
15 error::ErrorKind::*,
16};
817
9#[derive(Debug, Trace, Clone, Educe)]18#[derive(Debug, Trace, Clone, Educe)]
10#[educe(PartialEq)]19#[educe(PartialEq)]
11pub struct Context(#[educe(PartialEq(method = Cc::ptr_eq))] Cc<ContextInternal>);20pub struct Context(#[educe(PartialEq(method = Cc::ptr_eq))] pub(crate) Cc<ContextInternal>);
1221
13#[derive(Debug, Trace, Clone)]22#[derive(Trace)]
14struct ContextInternal {23pub(crate) struct ContextInternal {
15 sup_this: Option<SupThis>,24 /// Immutable, packed at closure-create time.
16 /// `bindings[i]` corresponds to `LocalId(offset + i)`.
17 bindings: Vec<Option<Thunk<Val>>>,25 pub(crate) captures: Cc<Vec<Thunk<Val>>>,
18 offset: u32,26 /// Filled during closure initialization
27 pub(crate) locals: Cc<LocalsFrame>,
19 parent: Option<Context>,28 pub(crate) sup_this: Option<SupThis>,
20}29}
2130
22impl Context {31impl Debug for ContextInternal {
23 pub fn new_future() -> Pending<Self> {32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 Pending::new()33 f.debug_struct("ContextInternal")
34 .field("captures", &self.captures.len())
35 .field("locals", &self.locals)
36 .field("sup_this", &self.sup_this.is_some())
37 .finish()
25 }38 }
39}
2640
41#[derive(Trace, Debug)]
42pub(crate) struct IterFrame {
27 pub fn sup_this(&self) -> Option<&SupThis> {43 slots: Vec<RefCell<Option<Thunk<Val>>>>,
44 captured: Cell<bool>,
45}
46impl IterFrame {
47 pub fn new(n: u16) -> IterFrame {
28 self.0.sup_this.as_ref()48 let cells: Vec<RefCell<Option<Thunk<Val>>>> = (0..n).map(|_| RefCell::new(None)).collect();
49 IterFrame {
50 slots: cells,
51 captured: Cell::new(false),
52 }
29 }53 }
30
31 pub fn try_sup_this(&self) -> Result<SupThis> {54 pub fn set(&self, slot: LocalSlot, value: Thunk<Val>) {
32 self.055 *self.slots[slot.0 as usize].borrow_mut() = Some(value);
33 .sup_this
34 .clone()
35 .ok_or_else(|| error!(CantUseSelfSupOutsideOfObject))
36 }56 }
57}
3758
59#[derive(Trace, Debug)]
60pub(crate) enum LocalsFrame {
38 /// Update binding in `CoW` fashion. Only useful for eager comprehension61 Once1(OnceCell<Thunk<Val>>),
62 /// Letrec/function/object/for frames - slots are filled during frame setup
39 /// fast-path, as it requires Cc refcount to be 1; Use `ContextBuilder` otherwise.63 Once(Vec<OnceCell<Thunk<Val>>>),
64 /// Comp-eager fast-path, cells are reset per iteration for the unique frames (i.e for the non-capturing thunks)
40 pub(crate) fn cow_fill_binding(&mut self, id: LocalId, value: Thunk<Val>) {65 Iter(IterFrame),
66}
67impl LocalsFrame {
68 pub fn set(&self, slot: LocalSlot, value: Thunk<Val>) {
41 let mut value = Some(Some(value));69 match self {
42
43 self.0.update_with(|inner| {70 LocalsFrame::Once1(cell) => {
44 let local_idx = (id.0 - inner.offset) as usize;71 debug_assert_eq!(slot.0, 0, "Once1 only holds slot 0");
45 while inner.bindings.len() <= local_idx {72 cell.set(value)
73 .map_err(|_| ())
46 inner.bindings.push(None);74 .expect("slot already filled");
47 }75 }
48 inner.bindings[local_idx] = value.take().expect("called once");76 LocalsFrame::Once(cells) => {
77 cells[slot.0 as usize]
78 .set(value)
79 .map_err(|_| ())
80 .expect("slot already filled");
49 });81 }
82 LocalsFrame::Iter(_) => unreachable!("iter frame has different constructors"),
83 }
50 }84 }
85}
5186
87impl LocalsFrame {
52 pub fn binding(&self, id: LocalId) -> Option<Thunk<Val>> {88 pub(crate) fn new_once(n: u16) -> Cc<Self> {
53 let id_num = id.0;89 if n == 1 {
54 if id_num >= self.0.offset {
55 let local_idx = (id_num - self.0.offset) as usize;
56 if let Some(Some(thunk)) = self.0.bindings.get(local_idx) {
57 return Some(thunk.clone());90 return Cc::new(Self::Once1(OnceCell::new()));
58 }
59 }91 }
60 if let Some(parent) = &self.0.parent {92 let cells: Vec<OnceCell<Thunk<Val>>> = (0..n).map(|_| OnceCell::new()).collect();
61 return parent.binding(id);
62 }93 Cc::new(Self::Once(cells))
63 None
64 }94 }
95}
6596
97pub(crate) struct IterContext {
66 #[must_use]98 context: Context,
99}
100impl IterContext {
67 pub fn into_future(self, ctx: Pending<Self>) -> Self {101 pub(crate) fn create(&self, build: impl FnOnce(&IterFrame)) -> Result<Context> {
68 {102 if !Cc::is_unique(&self.context.0.locals) {
69 ctx.clone().fill(self);103 bail!(EagerCompspecCaptured);
70 }104 }
71 ctx.unwrap()105 let LocalsFrame::Iter(frame) = &*self.context.0.locals else {
106 unreachable!("IterContext is only created for Iter ctx");
107 };
108 if frame.captured.get() {
109 bail!(EagerCompspecCaptured);
110 }
111 build(frame);
112 Ok(self.context.clone())
72 }113 }
73}114}
74115
75#[derive(Clone)]116#[derive(Trace, Clone)]
76pub struct ContextBuilder {117pub(crate) struct PackedContext {
118 captures: Cc<Vec<Thunk<Val>>>,
119 n_locals: u16,
120}
121impl PackedContext {
122 pub fn enter(self, sup_this: SupThis, build: impl FnOnce(&LocalsFrame, &Context)) -> Context {
123 let locals = LocalsFrame::new_once(self.n_locals);
124 let val = Context(Cc::new(ContextInternal {
125 captures: self.captures.clone(),
126 locals,
127 sup_this: Some(sup_this),
128 }));
129 build(&val.0.locals, &val);
130 val
131 }
132}
133#[derive(Trace, Clone, Educe, Debug)]
134#[educe(PartialEq)]
135pub(crate) struct PackedContextSupThis {
136 #[educe(PartialEq(method = Cc::ptr_eq))]
137 captures: Cc<Vec<Thunk<Val>>>,
138 n_locals: u16,
77 sup_this: Option<SupThis>,139 sup_this: Option<SupThis>,
78 bindings: Vec<Option<Thunk<Val>>>,
79 offset: u32,
80 parent: Option<Context>,
81}140}
141impl PackedContextSupThis {
142 pub fn enter(self, build: impl FnOnce(&LocalsFrame, &Context)) -> Context {
143 let locals = LocalsFrame::new_once(self.n_locals);
144 let val = Context(Cc::new(ContextInternal {
145 captures: self.captures.clone(),
146 locals,
147 sup_this: self.sup_this,
148 }));
149 build(&val.0.locals, &val);
150 val
151 }
152}
82153
83impl ContextBuilder {154impl Context {
84 pub fn new() -> Self {155 #[inline]
156 pub fn slot(&self, slot: LSlot) -> Thunk<Val> {
85 Self {157 match slot {
86 sup_this: None,158 LSlot::Local(i) => self.local(i),
87 bindings: Vec::new(),
88 offset: 0,159 LSlot::Capture(i) => self.capture(i),
89 parent: None,
90 }160 }
91 }161 }
92162 /// Read a local slot from the shared locals frame.
93 pub(crate) fn extend(parent: Context, capacity: usize) -> Self {163 ///
164 /// # Panics
165 /// If the slot has not yet been filled. The analyzer guarantees
166 /// that slot indices are in range and that letrec setup completes
167 /// before the first read. A panic indicates an analyzer/runtime
168 /// invariant violation, not a user error.
169 #[inline]
170 pub fn local(&self, slot: LocalSlot) -> Thunk<Val> {
94 let offset = parent.0.offset + parent.0.bindings.len() as u32;171 match &*self.0.locals {
172 LocalsFrame::Once1(cell) => {
173 debug_assert_eq!(slot.0, 0, "Once1 only holds slot 0");
174 cell.get().expect("local read before letrec init").clone()
95 Self {175 }
96 sup_this: parent.0.sup_this.clone(),176 LocalsFrame::Once(cells) => cells[slot.0 as usize]
177 .get()
178 .expect("local read before letrec init")
179 .clone(),
97 bindings: Vec::with_capacity(capacity),180 LocalsFrame::Iter(cells) => cells.slots[slot.0 as usize]
98 offset,181 .borrow()
99 parent: Some(parent),182 .as_ref()
183 .expect("iter local read before iteration filled it")
184 .clone(),
100 }185 }
101 }186 }
102187
103 pub(crate) fn bind(&mut self, id: LocalId, value: Thunk<Val>) {188 /// Read a captured slot from this closure's capture pack.
104 debug_assert!(189 #[inline]
105 id.0 >= self.offset,190 pub fn capture(&self, slot: CaptureSlot) -> Thunk<Val> {
106 "cannot bind {id:?} below offset {}",
107 self.offset,
108 );
109 let local_idx = (id.0 - self.offset) as usize;
110 self.bindings.reserve(local_idx);
111 while self.bindings.len() <= local_idx {
112 self.bindings.push(None);191 (*self.0.captures)[slot.0 as usize].clone()
113 }
114 self.bindings[local_idx] = Some(value);
115 }192 }
116193
117 pub(crate) fn build(self) -> Context {194 pub fn sup_this(&self) -> Option<&SupThis> {
195 self.0.sup_this.as_ref()
196 }
197
198 pub fn try_sup_this(&self) -> Result<SupThis> {
118 Context(Cc::new(ContextInternal {199 self.0
200 .sup_this
201 .clone()
202 .ok_or_else(|| error!(CantUseSelfSupOutsideOfObject))
203 }
204
205 /// Build a root context: empty captures, externals filled into a
206 /// fresh Once locals frame in declaration order. Used once at
207 /// program entry to construct the context the analyzed root LIR
208 /// runs against.
209 pub(crate) fn root(externals: Vec<Thunk<Val>>) -> Self {
119 sup_this: self.sup_this,210 let n: u16 = externals
211 .len()
212 .try_into()
213 .expect("more than u16::MAX externals");
214 let cells: Vec<OnceCell<Thunk<Val>>> = externals
215 .into_iter()
120 bindings: self.bindings,216 .map(|t| {
217 let cell = OnceCell::new();
218 cell.set(t).map_err(|_| ()).expect("fresh cell");
219 cell
220 })
221 .collect();
222 debug_assert_eq!(cells.len(), n as usize);
121 offset: self.offset,223 let locals = Cc::new(LocalsFrame::Once(cells));
224 Self(Cc::new(ContextInternal {
225 captures: Cc::new(Vec::new()),
122 parent: self.parent,226 locals,
227 sup_this: None,
123 }))228 }))
124 }229 }
125230
126 pub(crate) fn build_sup_this(mut self, st: SupThis) -> Context {231 pub(crate) fn pack_captures(&self, shape: &ClosureShape) -> PackedContext {
127 self.sup_this = Some(st);232 PackedContext {
233 captures: Cc::new(pack_captures(self, &shape.captures)),
128 self.build()234 n_locals: shape.n_locals,
235 }
129 }236 }
237 pub(crate) fn pack_captures_sup_this(&self, shape: &ClosureShape) -> PackedContextSupThis {
238 PackedContextSupThis {
239 captures: Cc::new(pack_captures(self, &shape.captures)),
240 n_locals: shape.n_locals,
241 sup_this: self.0.sup_this.clone(),
242 }
243 }
244
245 pub(crate) fn enter_iter(
246 parent: &Context,
247 shape: &ClosureShape,
248 cb: impl FnOnce(IterContext) -> Result<()>,
249 ) -> Result<()> {
250 let captures = Cc::new(pack_captures(parent, &shape.captures));
251 let locals = IterFrame::new(shape.n_locals);
252 cb(IterContext {
253 context: Self(Cc::new(ContextInternal {
254 captures,
255 locals: Cc::new(LocalsFrame::Iter(locals)),
256 sup_this: parent.0.sup_this.clone(),
257 })),
258 })
259 }
260
261 pub(crate) fn enter_using(parent: &Context, shape: &ClosureShape) -> Self {
262 debug_assert_eq!(shape.n_locals, 0);
263 if shape.captures.is_empty() {
264 if let LocalsFrame::Iter(i) = &*parent.0.locals {
265 i.captured.set(true);
266 }
267 // Value never uses captures, thus evaluating it against the parent gives the same result
268 return parent.clone();
269 }
270 let captures = Cc::new(pack_captures(parent, &shape.captures));
271 Self(Cc::new(ContextInternal {
272 captures,
273 locals: parent.0.locals.clone(),
274 sup_this: parent.0.sup_this.clone(),
275 }))
276 }
130}277}
131278
132impl Default for ContextBuilder {279fn pack_captures(parent: &Context, sources: &[LSlot]) -> Vec<Thunk<Val>> {
133 fn default() -> Self {
134 Self::new()280 sources.iter().map(|src| parent.slot(*src)).collect()
135 }
136}281}
137282
138pub struct InitialContextBuilder {283pub struct InitialContextBuilder {
139 builder: ContextBuilder,
140 externals: Vec<(IStr, LocalId)>,284 externals: Vec<(IStr, LocalId)>,
285 values: Vec<Thunk<Val>>,
141 next_id: u32,286 next_id: u32,
142}287}
143288
144impl InitialContextBuilder {289impl InitialContextBuilder {
145 pub(crate) fn new() -> Self {290 pub(crate) fn new() -> Self {
146 Self {291 Self {
147 builder: ContextBuilder::new(),
148 externals: Vec::new(),292 externals: Vec::new(),
293 values: Vec::new(),
149 next_id: 0,294 next_id: 0,
150 }295 }
151 }296 }
155 let id = LocalId(self.next_id);300 let id = LocalId(self.next_id);
156 self.next_id += 1;301 self.next_id += 1;
157 self.externals.push((name, id));302 self.externals.push((name, id));
158 self.builder.bind(id, value);303 self.values.push(value);
159 }304 }
160305
161 pub(crate) fn build(self) -> (ContextBuilder, Vec<(IStr, LocalId)>) {306 pub(crate) fn build(self) -> (Vec<(IStr, LocalId)>, Vec<Thunk<Val>>) {
162 (self.builder, self.externals)307 (self.externals, self.values)
163 }308 }
164}309}
165310
modifiedcrates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth
1use std::{cell::OnceCell, hash::Hasher, ptr::addr_of};1use std::{hash::Hasher, ptr::addr_of};
22
3use educe::Educe;
4use jrsonnet_gcmodule::{Cc, Trace};3use jrsonnet_gcmodule::Cc;
5
6use crate::{Result, bail, error::ErrorKind::InfiniteRecursionDetected, val::ThunkValue};
7
8#[derive(Trace, Educe)]
9#[educe(Clone)]
10pub struct Pending<V: Trace + 'static>(pub Cc<OnceCell<V>>);
11impl<T: Trace + 'static> Pending<T> {
12 pub fn new() -> Self {
13 Self(Cc::new(OnceCell::new()))
14 }
15 pub fn new_filled(v: T) -> Self {
16 let cell = OnceCell::new();
17 let _ = cell.set(v);
18 Self(Cc::new(cell))
19 }
20 /// # Panics
21 /// If wrapper is filled already
22 pub fn fill(self, value: T) {
23 self.0
24 .set(value)
25 .map_err(|_| ())
26 .expect("wrapper is filled already");
27 }
28}
29impl<T: Trace + 'static + Clone> Pending<T> {
30 /// # Panics
31 /// If wrapper is not yet filled
32 pub fn unwrap(&self) -> T {
33 self.0.get().cloned().expect("pending was not filled")
34 }
35 pub fn try_get(&self) -> Option<T> {
36 self.0.get().cloned()
37 }
38}
39
40impl<T: Trace + Clone> ThunkValue for Pending<T> {
41 type Output = T;
42
43 fn get(&self) -> Result<Self::Output> {
44 let Some(value) = self.0.get() else {
45 // TODO: Other error?
46 bail!(InfiniteRecursionDetected);
47 };
48 Ok(value.clone())
49 }
50}
51
52impl<T: Trace + 'static> Default for Pending<T> {
53 fn default() -> Self {
54 Self::new()
55 }
56}
574
58pub fn identity_hash<T, H: Hasher>(v: &Cc<T>, hasher: &mut H) {5pub fn identity_hash<T, H: Hasher>(v: &Cc<T>, hasher: &mut H) {
59 hasher.write_usize(addr_of!(**v) as usize);6 hasher.write_usize(addr_of!(**v) as usize);
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
1010
11use crate::{11use crate::{
12 ObjValue, ResolvePathOwned,12 ObjValue, ResolvePathOwned,
13 analyze::Diagnostic,
13 function::{CallLocation, FunctionSignature, ParamName},14 function::{CallLocation, FunctionSignature, ParamName},
14 stdlib::format::FormatError,15 stdlib::format::FormatError,
15 typed::TypeLocError,16 typed::TypeLocError,
112 CantUseSelfSupOutsideOfObject,113 CantUseSelfSupOutsideOfObject,
113114
114 #[error("static analysis errors: {}", .0.iter().map(|d| d.message.as_str()).collect::<Vec<_>>().join("; "))]115 #[error("static analysis errors: {}", .0.iter().map(|d| d.message.as_str()).collect::<Vec<_>>().join("; "))]
115 StaticAnalysisError(Vec<crate::analyze::Diagnostic>),116 StaticAnalysisError(Vec<Diagnostic>),
116 #[error("no super found")]117 #[error("no super found")]
117 NoSuperFound,118 NoSuperFound,
118119
119 #[error("for loop can only iterate over arrays")]120 #[error("for loop can only iterate over arrays")]
120 InComprehensionCanOnlyIterateOverArray,121 InComprehensionCanOnlyIterateOverArray,
122 #[error("(should not be visible) eager compspec evaluation failed due to captured context")]
123 EagerCompspecCaptured,
121124
122 #[error("array out of bounds: {0} is not within [0,{1})")]125 #[error("array out of bounds: {0} is not within [0,{1})")]
123 ArrayBoundsError(isize, u32),126 ArrayBoundsError(isize, u32),
397 };400 };
398}401}
399
400#[macro_export]
401macro_rules! runtime_error {
402 ($l:literal$(, $($tt:tt)*)?) => {
403 $crate::error::Error::from($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)))
404 };
405}
406402
modifiedcrates/jrsonnet-evaluator/src/evaluate/compspec.rsdiffbeforeafterboth
3use jrsonnet_types::ValType;3use jrsonnet_types::ValType;
44
5use super::{5use super::{
6 destructure::{self, evaluate_locals, evaluate_locals_unbound},6 destructure::{destruct, evaluate_locals_unbound, fill_letrec_binds},
7 evaluate_field_member_static, evaluate_field_member_unbound,7 evaluate_field_member_static, evaluate_field_member_unbound,
8};8};
9use crate::{9use crate::{
10 Context, ContextBuilder, ObjValue, ObjValueBuilder, Pending, Result, Thunk, Val,10 Context, ObjValue, ObjValueBuilder, Result, Thunk, Val,
11 analyze::{LArrComp, LBind, LCompSpec, LDestruct, LExpr, LFieldMember, LObjComp, LocalId},11 analyze::{
12 ClosureShape, LArrComp, LBind, LCompSpec, LDestruct, LExpr, LFieldMember, LObjComp,
13 LocalSlot,
14 },
12 arr::ArrValue,15 arr::ArrValue,
13 bail,16 bail,
14 error::ErrorKind::*,17 error::ErrorKind::*,
15 evaluate::evaluate,18 evaluate::{evaluate, evaluate_trivial},
16};19};
1720
18trait CompCollector {21trait CompCollector {
2225
23struct EagerArrCollector<'a> {26struct EagerArrCollector<'a> {
24 out: &'a mut Vec<Val>,27 out: &'a mut Vec<Val>,
28 value_shape: &'a ClosureShape,
25 value: &'a LExpr,29 value: &'a LExpr,
26}30}
27impl CompCollector for EagerArrCollector<'_> {31impl CompCollector for EagerArrCollector<'_> {
28 fn reserve(&mut self, size_hint: usize) {32 fn reserve(&mut self, size_hint: usize) {
29 self.out.reserve(size_hint);33 self.out.reserve(size_hint);
30 }34 }
31 fn collect(&mut self, ctx: Context) -> Result<()> {35 fn collect(&mut self, ctx: Context) -> Result<()> {
36 if let Some(v) = evaluate_trivial(self.value) {
37 self.out.push(v);
38 return Ok(());
39 }
40 if let LExpr::Slot(slot) = self.value {
41 self.out.push(ctx.slot(*slot).evaluate()?);
42 return Ok(());
43 }
44 let env = Context::enter_using(&ctx, self.value_shape);
32 self.out.push(evaluate(ctx, self.value)?);45 self.out.push(evaluate(env, self.value)?);
33 Ok(())46 Ok(())
34 }47 }
35}48}
3649
37struct LazyArrCollector<'a> {50struct LazyArrCollector<'a> {
38 out: &'a mut Vec<Thunk<Val>>,51 out: &'a mut Vec<Thunk<Val>>,
52 value_shape: &'a ClosureShape,
39 value: &'a Rc<LExpr>,53 value: &'a Rc<LExpr>,
40}54}
41impl CompCollector for LazyArrCollector<'_> {55impl CompCollector for LazyArrCollector<'_> {
42 fn reserve(&mut self, size_hint: usize) {56 fn reserve(&mut self, size_hint: usize) {
43 self.out.reserve(size_hint);57 self.out.reserve(size_hint);
44 }58 }
45 fn collect(&mut self, ctx: Context) -> Result<()> {59 fn collect(&mut self, ctx: Context) -> Result<()> {
60 if let Some(v) = evaluate_trivial(self.value) {
61 self.out.push(Thunk::evaluated(v));
62 return Ok(());
63 }
64 if let LExpr::Slot(slot) = self.value.as_ref() {
65 self.out.push(ctx.slot(*slot));
66 return Ok(());
67 }
68 let env = Context::enter_using(&ctx, self.value_shape);
46 let value_expr = self.value.clone();69 let value_expr = self.value.clone();
47 self.out.push(Thunk!(move || evaluate(ctx, &value_expr)));70 self.out.push(Thunk!(move || evaluate(env, &value_expr)));
48 Ok(())71 Ok(())
49 }72 }
50}73}
5174
52struct ObjCompCollectorStatic<'a> {75struct ObjCompCollectorStatic<'a> {
53 builder: &'a mut ObjValueBuilder,76 builder: &'a mut ObjValueBuilder,
77 frame_shape: &'a ClosureShape,
54 locals: &'a [LBind],78 locals: &'a [LBind],
55 field: &'a LFieldMember,79 field: &'a LFieldMember,
56}80}
59 self.builder.reserve_fields(guaranteed);83 self.builder.reserve_fields(guaranteed);
60 }84 }
61 fn collect(&mut self, inner_ctx: Context) -> Result<()> {85 fn collect(&mut self, inner_ctx: Context) -> Result<()> {
86 // Build the object's A-frame fresh per iteration: captures from
87 // the comp's iter ctx, locals = `this` (slot 0, unfilled in the
88 // static path) + member-locals via letrec.
62 let value_ctx = evaluate_locals(inner_ctx.clone(), self.locals);89 let value_ctx = inner_ctx
90 .pack_captures_sup_this(self.frame_shape)
91 .enter(|fill, ctx| {
92 fill_letrec_binds(fill, &ctx, self.locals);
93 });
63 evaluate_field_member_static(self.builder, inner_ctx, value_ctx, self.field)94 evaluate_field_member_static(self.builder, inner_ctx, value_ctx, self.field)
64 }95 }
65}96}
6697
67struct ObjCompCollectorUnbound<'a> {98struct ObjCompCollectorUnbound<'a> {
68 builder: &'a mut ObjValueBuilder,99 builder: &'a mut ObjValueBuilder,
100 frame_shape: Rc<ClosureShape>,
69 locals: Rc<Vec<LBind>>,101 locals: Rc<Vec<LBind>>,
70 this_id: Option<LocalId>,102 this_slot: Option<LocalSlot>,
71 field: &'a LFieldMember,103 field: &'a LFieldMember,
72}104}
73impl CompCollector for ObjCompCollectorUnbound<'_> {105impl CompCollector for ObjCompCollectorUnbound<'_> {
74 fn reserve(&mut self, guaranteed: usize) {106 fn reserve(&mut self, guaranteed: usize) {
75 self.builder.reserve_fields(guaranteed);107 self.builder.reserve_fields(guaranteed);
76 }108 }
77 fn collect(&mut self, inner_ctx: Context) -> Result<()> {109 fn collect(&mut self, inner_ctx: Context) -> Result<()> {
78 let uctx = evaluate_locals_unbound(inner_ctx.clone(), self.locals.clone(), self.this_id);110 let uctx = evaluate_locals_unbound(
111 &inner_ctx,
112 &self.frame_shape,
113 self.this_slot,
114 self.locals.clone(),
115 );
79 evaluate_field_member_unbound(self.builder, inner_ctx, uctx, self.field)116 evaluate_field_member_unbound(self.builder, inner_ctx, uctx, self.field)
100 0,137 0,
101 &mut ObjCompCollectorUnbound {138 &mut ObjCompCollectorUnbound {
102 builder: &mut builder,139 builder: &mut builder,
140 frame_shape: comp.frame_shape.clone(),
103 locals: comp.locals.clone(),141 locals: comp.locals.clone(),
104 this_id: comp.this,142 this_slot: comp.this,
105 field: &comp.field,143 field: &comp.field,
106 },144 },
107 )?;145 )?;
114 0,152 0,
115 &mut ObjCompCollectorStatic {153 &mut ObjCompCollectorStatic {
116 builder: &mut builder,154 builder: &mut builder,
155 frame_shape: &comp.frame_shape,
117 locals: &comp.locals,156 locals: &comp.locals,
118 field: &comp.field,157 field: &comp.field,
119 },158 },
126pub fn evaluate_arr_comp(ctx: Context, comp: &LArrComp) -> Result<Val> {165pub fn evaluate_arr_comp(ctx: Context, comp: &LArrComp) -> Result<Val> {
127 let cached_overs = cache_overs(&ctx, &comp.compspecs)?;166 let cached_overs = cache_overs(&ctx, &comp.compspecs)?;
128167
129 // In eager evaluation, Context is not captured, thus updates in CoW fashion will likely to success168 // Eager fast-path: when the comp has only `if` and `for { destruct: Full(_) }`
169 // specs, allocate one Iter A-frame per for-spec and re-set the slot
170 // per iteration as long as the frame's refcount stays at 1.
130 'eager: {171 'eager: {
131 let mut out = Vec::new();172 let mut out = Vec::new();
132173
147 0,188 0,
148 &mut EagerArrCollector {189 &mut EagerArrCollector {
149 out: &mut out,190 out: &mut out,
191 value_shape: &comp.value_shape,
150 value: &comp.value,192 value: &comp.value,
151 },193 },
152 )194 )
166 0,208 0,
167 &mut LazyArrCollector {209 &mut LazyArrCollector {
168 out: &mut items,210 out: &mut items,
211 value_shape: &comp.value_shape,
169 value: &comp.value,212 value: &comp.value,
170 },213 },
171 )?;214 )?;
221 }264 }
222 }265 }
223 LCompSpec::For { destruct, over, .. } => {266 LCompSpec::For {
267 frame_shape,
268 destruct,
269 over,
270 ..
232 };280 };
233 let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;281 let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;
234 match destruct {282 match destruct {
235 LDestruct::Full(id) => {283 LDestruct::Full(slot) => {
236 let id = *id;284 Context::enter_iter(&ctx, frame_shape, |it| {
237 let mut inner_ctx = ContextBuilder::extend(ctx, 1).build();
238 for (i, item) in arr.iter().enumerate() {285 for (i, item) in arr.iter().enumerate() {
239 // TODO: reuse one ContextBuilder for full evaluate_compspecs pipeline286 let item = item?;
240 inner_ctx.cow_fill_binding(id, Thunk::evaluated(item?));287 let ctx = it.create(|f| {
288 f.set(*slot, Thunk::evaluated(item));
289 })?;
241 evaluate_compspecs_eager(290 evaluate_compspecs_eager(
242 inner_ctx.clone(),291 ctx,
243 specs,292 specs,
244 cached_overs,293 cached_overs,
245 idx + 1,294 idx + 1,
246 if i == 0 { inner_reserve } else { 0 },295 if i == 0 { inner_reserve } else { 0 },
247 collector,296 collector,
248 )?;297 )?;
249 }298 }
299 Ok(())
300 })?;
250 }301 }
251 // TODO: Should not be eager? CoW won't work here302 // TODO: Should not be eager? CoW won't work here
252 #[cfg(feature = "exp-destruct")]303 #[cfg(feature = "exp-destruct")]
284 }335 }
285 }336 }
286 LCompSpec::For { destruct, over, .. } => {337 LCompSpec::For {
338 frame_shape,
339 destruct: dst,
340 over,
341 ..
342 } => {
295 };351 };
296 let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;352 let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;
297 for (i, item) in arr.iter().enumerate() {353 for (i, item) in arr.iter().enumerate() {
298 let item_val = item?;354 let item = item?;
299 let mut inner_builder = ContextBuilder::extend(ctx.clone(), 1);355 let inner_ctx = ctx.pack_captures_sup_this(frame_shape).enter(|fill, ctx| {
300 let fctx = Pending::new();
301 destructure::destruct(
302 destruct,356 destruct(dst, fill, Thunk::evaluated(item), &ctx);
303 Thunk::evaluated(item_val),
304 fctx.clone(),
305 &mut inner_builder,
306 );
307 let inner_ctx = inner_builder.build().into_future(fctx);357 });
308 evaluate_compspecs(358 evaluate_compspecs(
309 inner_ctx,359 inner_ctx,
310 specs,360 specs,
modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
3use jrsonnet_gcmodule::Trace;3use jrsonnet_gcmodule::Trace;
44
5use crate::{5use crate::{
6 Context, ContextBuilder, Pending, Result, SupThis, Thunk, Unbound, Val,6 Context, LocalsFrame, PackedContext, Result, SupThis, Thunk, Unbound, Val,
7 analyze::{LBind, LDestruct, LDestructField, LDestructRest, LExpr, LocalId},7 analyze::{
8 ClosureShape, LBind, LDestruct, LDestructField, LDestructRest, LExpr, LLocalExpr, LocalSlot,
9 },
8 bail,10 bail,
9 evaluate::evaluate,11 evaluate::evaluate,
15 rest: Option<&LDestructRest>,17 rest: Option<&LDestructRest>,
16 end: &[LDestruct],18 end: &[LDestruct],
1719
20 fill: &LocalsFrame,
18 value: Thunk<Val>,21 value: Thunk<Val>,
19 fctx: Pending<Context>,
20 builder: &mut ContextBuilder,22 a_ctx: &Context,
21) {23) {
22 let min_len = start.len() + end.len();24 let min_len = start.len() + end.len();
23 let has_rest = rest.is_some();25 let has_rest = rest.is_some();
44 let full = full.clone();46 let full = full.clone();
45 destruct(47 destruct(
46 d,48 d,
49 fill,
47 Thunk!(move || Ok(full.evaluate()?.get(i as u32)?.expect("length is checked"))),50 Thunk!(move || Ok(full.evaluate()?.get(i as u32)?.expect("length is checked"))),
48 fctx.clone(),51 a_ctx,
49 builder,
50 );52 );
51 }53 }
5254
53 let start_len = start.len() as u32;55 let start_len = start.len() as u32;
54 let end_len = end.len() as u32;56 let end_len = end.len() as u32;
5557
56 if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {58 if let Some(LDestructRest::Keep(slot)) = rest {
57 let full = full.clone();59 let full = full.clone();
58 builder.bind(60 fill.set(
59 *id,61 *slot,
60 Thunk!(move || {62 Thunk!(move || {
61 let full = full.evaluate()?;63 let full = full.evaluate()?;
62 let to = full.len() - end_len;64 let to = full.len() - end_len;
73 let full = full.clone();75 let full = full.clone();
74 destruct(76 destruct(
75 d,77 d,
78 fill,
76 Thunk!(move || {79 Thunk!(move || {
77 let full = full.evaluate()?;80 let full = full.evaluate()?;
78 Ok(full81 Ok(full
79 .get(full.len() - end_len + i as u32)?82 .get(full.len() - end_len + i as u32)?
80 .expect("length is checked"))83 .expect("length is checked"))
81 }),84 }),
82 fctx.clone(),85 a_ctx,
83 builder,
84 );86 );
85 }87 }
86}88}
90 fields: &[LDestructField],92 fields: &[LDestructField],
91 rest: Option<&LDestructRest>,93 rest: Option<&LDestructRest>,
9294
95 fill: &LocalsFrame,
93 value: Thunk<Val>,96 value: Thunk<Val>,
94 fctx: Pending<Context>,
95 builder: &mut ContextBuilder,97 a_ctx: &Context,
96) {98) {
97 use jrsonnet_interner::IStr;99 use jrsonnet_interner::IStr;
98 use rustc_hash::FxHashSet;100 use rustc_hash::FxHashSet;
124 Ok(obj)126 Ok(obj)
125 });127 });
126128
127 if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {129 if let Some(LDestructRest::Keep(slot)) = rest {
128 let full = full.clone();130 let full = full.clone();
129 builder.bind(131 fill.set(
130 *id,132 *slot,
131 Thunk!(move || {133 Thunk!(move || {
132 let full = full.evaluate()?;134 let full = full.evaluate()?;
133 let mut out = ObjValueBuilder::new();135 let mut out = ObjValueBuilder::new();
140142
141 for field in fields {143 for field in fields {
142 let field_name = field.name.clone();144 let field_name = field.name.clone();
143 let default: Option<(Pending<Context>, Rc<LExpr>)> =145 let default_thunk: Option<Thunk<Val>> = field
144 field.default.as_ref().map(|e| (fctx.clone(), e.clone()));146 .default
147 .as_ref()
148 .map(|(shape, expr)| build_b_thunk(a_ctx, shape, expr.clone()));
149
145 let field_full = full.clone();150 let field_full = full.clone();
146 let value_thunk = Thunk!(move || {151 let value_thunk = Thunk!(move || {
147 let obj = field_full.evaluate()?;152 let obj = field_full.evaluate()?;
148 obj.get(field_name)?.map_or_else(153 obj.get(field_name)?.map_or_else(
149 || {154 || default_thunk.as_ref().expect("shape is checked").evaluate(),
150 let (fctx, expr) = default.as_ref().expect("shape is checked");
151 evaluate(fctx.unwrap(), expr)
152 },
153 Ok,155 Ok,
154 )156 )
155 });157 });
156158
157 if let Some(into) = &field.into {159 if let Some(into) = &field.into {
158 destruct(into, value_thunk, fctx.clone(), builder);160 destruct(into, fill, value_thunk, a_ctx);
159 } else {161 } else {
160 unreachable!("analyzer lowers object-destruct shorthands into `into`");162 unreachable!("analyzer lowers object-destruct shorthands into `into`");
161 }163 }
162 }164 }
163}165}
164166
165/// Bind a pre-built thunk to an [`LDestruct`] pattern, inserting one
166/// binding per [`LocalId`] the pattern introduces.
167///
168/// `fctx` is needed for object-destruct defaults (feature `exp-destruct`).
169#[allow(unused_variables)]167#[allow(unused_variables)]
170pub fn destruct(168pub fn destruct(d: &LDestruct, fill: &LocalsFrame, value: Thunk<Val>, a_ctx: &Context) {
171 d: &LDestruct,
172 value: Thunk<Val>,
173 fctx: Pending<Context>,
174 builder: &mut ContextBuilder,
175) {
176 match d {169 match d {
177 LDestruct::Full(id) => builder.bind(*id, value),170 LDestruct::Full(slot) => fill.set(*slot, value),
178 #[cfg(feature = "exp-destruct")]171 #[cfg(feature = "exp-destruct")]
179 LDestruct::Skip => {}172 LDestruct::Skip => {}
180 #[cfg(feature = "exp-destruct")]173 #[cfg(feature = "exp-destruct")]
181 LDestruct::Array { start, rest, end } => {174 LDestruct::Array { start, rest, end } => {
182 destruct_array(start, rest.as_ref(), end, value, fctx, builder)175 destruct_array(start, rest.as_ref(), end, fill, value, a_ctx)
183 }176 }
184 #[cfg(feature = "exp-destruct")]177 #[cfg(feature = "exp-destruct")]
185 LDestruct::Object { fields, rest } => {178 LDestruct::Object { fields, rest } => destruct_object(fields, rest.as_ref(), fill, value, a_ctx),
186 destruct_object(fields, rest.as_ref(), value, fctx, builder)
187 }
188 }179 }
189}180}
190181
191/// Bind one [`LBind`] as a lazy thunk that evaluates in the given
192/// future context. Mirrors the old `evaluate_dest` — one entry per
193/// binding in a `local … ;` frame.
194pub fn evaluate_dest(bind: &LBind, fctx: Pending<Context>, builder: &mut ContextBuilder) {182pub fn build_b_thunk(a_ctx: &Context, shape: &ClosureShape, expr: Rc<LExpr>) -> Thunk<Val> {
195 let value = bind.value.clone();183 let env = Context::enter_using(a_ctx, shape);
196 let fctx_clone = fctx.clone();
197 let thunk = Thunk!(move || {184 Thunk!(move || evaluate(env, &expr))
198 let ctx = fctx_clone.unwrap();
199 evaluate(ctx, &value)
200 });
201 destruct(&bind.destruct, thunk, fctx, builder);
202}185}
203186pub fn build_b_thunk_uno(a_ctx: &Context, shape: Rc<(ClosureShape, LExpr)>) -> Thunk<Val> {
204/// Bind each LBind's value as a lazy thunk. Mutually recursive locals187 let env = Context::enter_using(a_ctx, &shape.0);
205/// resolve lazily through the shared Pending<Context>.188 Thunk!(move || evaluate(env, &shape.1))
189}
190
206pub fn evaluate_locals(parent: Context, binds: &[LBind]) -> Context {191pub fn fill_letrec_binds(fill: &LocalsFrame, ctx: &Context, binds: &[LBind]) {
207 if binds.is_empty() {192 for bind in binds {
193 let value_thunk = build_b_thunk(ctx, &bind.value_shape, bind.value.clone());
194 destruct(&bind.destruct, fill, value_thunk, ctx);
195 }
196}
197
208 return parent;198pub fn evaluate_local_expr(parent: Context, l: &LLocalExpr) -> Result<Val> {
209 }
210 let fctx = Context::new_future();
211 let mut builder =199 let ctx = parent
200 .pack_captures_sup_this(&l.frame_shape)
212 ContextBuilder::extend(parent, binds.iter().map(|b| b.destruct.ids().len()).sum());201 .enter(|fill, ctx| {
213 for bind in binds {202 fill_letrec_binds(fill, ctx, &l.binds);
203 });
214 evaluate_dest(bind, fctx.clone(), &mut builder);204 evaluate(ctx, &l.body)
215 }205}
216 builder.build().into_future(fctx)
217}
218206
219pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}207pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}
220impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}208impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}
221209
222pub fn evaluate_locals_unbound(210pub fn evaluate_locals_unbound(
223 fctx: Context,211 outer: &Context,
212 frame_shape: &ClosureShape,
213 this_slot: Option<LocalSlot>,
224 locals: Rc<Vec<LBind>>,214 locals: Rc<Vec<LBind>>,
225 this_id: Option<LocalId>,
226) -> impl CloneableUnbound<Context> {215) -> impl CloneableUnbound<Context> {
227 #[derive(Trace, Clone)]216 #[derive(Trace, Clone)]
228 struct UnboundLocals {217 struct UnboundLocals {
229 fctx: Context,218 captures: PackedContext,
219 this_slot: Option<LocalSlot>,
230 locals: Rc<Vec<LBind>>,220 locals: Rc<Vec<LBind>>,
231 this_id: Option<LocalId>,
232 }221 }
233 impl Unbound for UnboundLocals {222 impl Unbound for UnboundLocals {
234 type Bound = Context;223 type Bound = Context;
235224
236 fn bind(&self, sup_this: SupThis) -> Result<Context> {225 fn bind(&self, sup_this: SupThis) -> Result<Context> {
237 let parent = self.fctx.clone();226 Ok(self.captures.clone().enter(sup_this, |fill, ctx| {
238
239 let fctx = Context::new_future();
240 let mut builder = ContextBuilder::extend(
241 parent,
242 self.locals.iter().map(|b| b.destruct.ids().len()).sum(),
243 );
244 for b in self.locals.iter() {
245 evaluate_dest(b, fctx.clone(), &mut builder);
246 }
247 if let Some(this_id) = self.this_id {227 if let Some(slot) = self.this_slot {
228 let this_obj = ctx.sup_this().expect("sup_this set above").this().clone();
248 builder.bind(this_id, Thunk::evaluated(Val::Obj(sup_this.this().clone())));229 fill.set(slot, Thunk::evaluated(Val::Obj(this_obj)));
249 }230 }
250 let ctx = builder.build_sup_this(sup_this).into_future(fctx);231 fill_letrec_binds(fill, ctx, &self.locals);
251 Ok(ctx)232 }))
252 }233 }
253 }234 }
254235
255 UnboundLocals {236 UnboundLocals {
256 fctx,237 captures: outer.pack_captures(frame_shape),
238 this_slot,
257 locals,239 locals,
258 this_id,
259 }240 }
260}241}
261242
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
77
8use self::{8use self::{
9 compspec::{evaluate_arr_comp, evaluate_obj_comp},9 compspec::{evaluate_arr_comp, evaluate_obj_comp},
10 destructure::{evaluate_locals, evaluate_locals_unbound},10 destructure::{build_b_thunk_uno, evaluate_local_expr, evaluate_locals_unbound},
11 operator::evaluate_binary_op_special,11 operator::evaluate_binary_op_special,
12};12};
13use crate::{13use crate::{
14 Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Result, ResultExt as _, SupThis,14 Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Result, ResultExt as _, SupThis,
15 Unbound, Val,15 Unbound, Val,
16 analyze::{16 analyze::{
17 LArgsDesc, LAssertStmt, LExpr, LFieldMember, LFieldName, LFunction, LIndexPart, LObjBody,17 ClosureShape, LArgsDesc, LAssertStmt, LExpr, LFieldMember, LFieldName, LFunction,
18 LObjMembers,18 LIndexPart, LObjAsserts, LObjBody, LObjMembers, LSlot,
19 },19 },
20 arr::ArrValue,
20 bail,21 bail, error,
21 error::{ErrorKind::*, suggest_object_fields},22 error::{ErrorKind::*, suggest_object_fields},
22 evaluate::operator::evaluate_unary_op,23 evaluate::{destructure::fill_letrec_binds, operator::evaluate_unary_op},
23 function::{CallLocation, FuncDesc, FuncVal, prepared::PreparedFuncVal},24 function::{CallLocation, FuncDesc, FuncVal, prepared::PreparedFuncVal},
24 in_frame, runtime_error,25 in_frame,
25 typed::FromUntyped as _,26 typed::FromUntyped as _,
26 val::{CachedUnbound, Thunk},27 val::{CachedUnbound, Thunk},
27 with_state,28 with_state,
62 })63 })
63}64}
6465
65/// Evaluate a method definition.
66pub fn evaluate_method(ctx: Context, name: IStr, func: &Rc<LFunction>) -> Val {66pub fn evaluate_method(ctx: Context, name: IStr, func: &Rc<LFunction>) -> Val {
67 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {67 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {
68 name,68 name,
69 ctx,69 body_captures: ctx.pack_captures_sup_this(&func.body_shape),
70 func: func.clone(),70 func: func.clone(),
71 })))71 })))
72}72}
91}91}
9292
93pub fn evaluate_thunk(ctx: Context, expr: Rc<LExpr>, tailstrict: bool) -> Result<Thunk<Val>> {93pub fn evaluate_thunk(ctx: Context, expr: Rc<LExpr>, tailstrict: bool) -> Result<Thunk<Val>> {
94 match &*expr {
95 LExpr::Slot(LSlot::Local(i)) => return Ok(ctx.local(*i)),
96 LExpr::Slot(LSlot::Capture(i)) => return Ok(ctx.capture(*i)),
97 _ => {
98 if let Some(v) = evaluate_trivial(&expr) {
99 return Ok(Thunk::evaluated(v));
100 }
101 }
102 }
94 Ok(if tailstrict {103 Ok(if tailstrict {
95 Thunk::evaluated(evaluate(ctx, &expr)?)104 Thunk::evaluated(evaluate(ctx, &expr)?)
96 } else {105 } else {
106 }115 }
107}116}
108
109pub fn evaluate_named(name: &IStr, ctx: Context, expr: &LExpr) -> Result<Val> {
110 if let LExpr::Function(f) = &expr {
111 return Ok(evaluate_method(
112 ctx,
113 f.name.clone().unwrap_or_else(|| name.clone()),
114 f,
115 ));
116 }
117 evaluate(ctx, expr)
118}
119117
120pub fn evaluate(ctx: Context, expr: &LExpr) -> Result<Val> {118pub fn evaluate(ctx: Context, expr: &LExpr) -> Result<Val> {
121 Ok(match expr {119 Ok(match expr {
122 LExpr::Null => Val::Null,120 LExpr::Null => Val::Null,
123 LExpr::Bool(b) => Val::Bool(*b),121 LExpr::Bool(b) => Val::Bool(*b),
124 LExpr::Str(s) => Val::string(s.clone()),122 LExpr::Str(s) => Val::string(s.clone()),
125 LExpr::Num(n) => Val::Num(*n),123 LExpr::Num(n) => Val::Num(*n),
126 LExpr::Local(id) => {124 LExpr::Slot(slot) => ctx.slot(*slot).evaluate()?,
127 let Some(thunk) = ctx.binding(*id) else {
128 bail!("should not happen: unbound local {id:?}");
129 };
130 thunk.evaluate()?
131 }
132 LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),125 LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),
133 LExpr::Arr(items) => Val::Arr(crate::arr::ArrValue::expr(ctx, items.clone())),126 LExpr::Arr { shape, items } => Val::Arr(ArrValue::expr(ctx, shape, items.clone())),
134 LExpr::UnaryOp(op, value) => {127 LExpr::UnaryOp(op, value) => {
135 let value = evaluate(ctx, value)?;128 let value = evaluate(ctx, value)?;
136 evaluate_unary_op(*op, &value)?129 evaluate_unary_op(*op, &value)?
137 }130 }
138 LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,131 LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,
139 LExpr::LocalExpr { binds, body } => {132 LExpr::LocalExpr(local_expr) => evaluate_local_expr(ctx, local_expr)?,
140 let ctx = evaluate_locals(ctx, binds);
141 evaluate(ctx, body)?
142 }
143 LExpr::IfElse {133 LExpr::IfElse {
144 cond,134 cond,
145 cond_then,135 cond_then,
176 func.name.clone().unwrap_or_else(names::anonymous),166 func.name.clone().unwrap_or_else(names::anonymous),
177 func,167 func,
178 ),168 ),
169 LExpr::IdentityFunction => Val::Func(FuncVal::identity()),
179 LExpr::Apply {170 LExpr::Apply {
180 applicable,171 applicable,
181 args,172 args,
240 let n = v.as_num().ok_or_else(|| -> crate::Error {231 let n = v.as_num().ok_or_else(|| -> crate::Error {
241 TypeMismatch("slice step", vec![ValType::Num], v.value_type()).into()232 TypeMismatch("slice step", vec![ValType::Num], v.value_type()).into()
242 })?;233 })?;
243 BoundedUsize::new(n as usize)234 BoundedUsize::new(n as usize).ok_or_else(|| error!("slice step must be >= 1"))
244 .ok_or_else(|| runtime_error!("slice step must be >= 1"))
245 })235 })
246 .transpose()?;236 .transpose()?;
247 Val::from(indexable.slice(start, end, step)?)237 Val::from(indexable.slice(start, end, step)?)
278 bail!(OnlyFunctionsCanBeCalledGot(func_val.value_type()))268 bail!(OnlyFunctionsCanBeCalledGot(func_val.value_type()))
279 };269 };
270
271 if func.is_identity() && args.names.is_empty() && args.unnamed.len() == 1 {
272 return evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?.evaluate();
273 }
280274
281 let name = func.name();275 let name = func.name();
276
277 if args.names.is_empty() && args.unnamed.len() == 1 && func.params().len() == 1 {
278 use crate::function::prepared::PreparedCall;
279 let prepared_inline = PreparedCall::empty();
280 let arg = evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?;
281 let arg_slice = std::slice::from_ref(&arg);
282 return in_frame(
283 loc,
284 || format!("function <{name}> call"),
285 || {
286 func.evaluate_prepared(
287 &prepared_inline,
288 CallLocation::native(),
289 arg_slice,
290 &[],
291 tailstrict,
292 )
293 },
294 );
295 }
296
282 let unnamed = args297 let unnamed = args
283 .unnamed298 .unnamed
286 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))301 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))
287 .collect::<Result<Vec<_>>>()?;302 .collect::<Result<Vec<_>>>()?;
303
304 // Fast path: positional-only multi-arg call fully covering the
305 // params, no defaults.
306 if args.names.is_empty() && unnamed.len() == func.params().len() {
307 use crate::function::prepared::PreparedCall;
308 let prepared_inline = PreparedCall::empty();
309 return in_frame(
310 loc,
311 || format!("function <{name}> call"),
312 || {
313 func.evaluate_prepared(
314 &prepared_inline,
315 CallLocation::native(),
316 &unnamed,
317 &[],
318 tailstrict,
319 )
320 },
321 );
322 }
288323
289 let named = args324 let named = args
290 .values325 .values
302}337}
303338
304fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {339fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {
305 let mut value = if let LExpr::Super = indexable {340 let mut value = if matches!(indexable, LExpr::Super) {
306 let sup_this = ctx.try_sup_this()?;341 let sup_this = ctx.try_sup_this()?;
307 // First part must be evaluated to get the super field name342 // First part must be evaluated to get the super field name
308 if parts.is_empty() {343 if parts.is_empty() {
422 #[derive(Trace)]457 #[derive(Trace)]
423 struct UnboundValue<B: Trace> {458 struct UnboundValue<B: Trace> {
424 uctx: B,459 uctx: B,
425 value: Rc<LExpr>,460 value: Rc<(ClosureShape, LExpr)>,
426 name: IStr,461 name: IStr,
427 }462 }
428 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {463 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {
429 type Bound = Val;464 type Bound = Val;
430 fn bind(&self, sup_this: SupThis) -> Result<Val> {465 fn bind(&self, sup_this: SupThis) -> Result<Val> {
431 evaluate(self.uctx.bind(sup_this)?, &self.value)466 let a_ctx = self.uctx.bind(sup_this)?;
467 let b_ctx = Context::enter_using(&a_ctx, &self.value.0);
468 evaluate(b_ctx, &self.value.1)
432 }469 }
433 }470 }
434471
468 return Ok(());505 return Ok(());
469 };506 };
470507
471 let value = value.clone();508 let thunk = build_b_thunk_uno(&value_ctx, value.clone());
472 builder509 builder
473 .field(name)510 .field(name)
474 .with_add(*plus)511 .with_add(*plus)
475 .with_visibility(*visibility)512 .with_visibility(*visibility)
476 .try_thunk(Thunk!(move || { evaluate(value_ctx, &value) }))?;513 .try_thunk(thunk)?;
477 Ok(())514 Ok(())
478}515}
479516
491528
492 if needs_unbound {529 if needs_unbound {
493 let uctx = CachedUnbound::new(evaluate_locals_unbound(530 let uctx = CachedUnbound::new(evaluate_locals_unbound(
531 &ctx,
494 ctx.clone(),532 &members.frame_shape,
533 members.this,
495 members.locals.clone(),534 members.locals.clone(),
496 members.this,
497 ));535 ));
498 for field in &members.fields {536 for field in &members.fields {
499 evaluate_field_member_unbound(&mut builder, ctx.clone(), uctx.clone(), field)?;537 evaluate_field_member_unbound(&mut builder, ctx.clone(), uctx.clone(), field)?;
500 }538 }
501 if !members.asserts.is_empty() {539 if let Some(asserts_block) = &members.asserts {
502 builder.assert(evaluate_object_assertions_unbound(540 builder.assert(evaluate_object_assertions_unbound(
503 uctx,541 uctx,
504 members.asserts.clone(),542 asserts_block.clone(),
505 ));543 ));
506 }544 }
507 } else {545 } else {
508 let field_ctx = ctx;
509 let value_ctx = evaluate_locals(field_ctx.clone(), &members.locals);546 let a_ctx = ctx
547 .pack_captures_sup_this(&members.frame_shape)
548 .enter(|fill, ctx| {
549 fill_letrec_binds(fill, &ctx, &members.locals);
550 });
510 for field in &members.fields {551 for field in &members.fields {
511 evaluate_field_member_static(552 evaluate_field_member_static(&mut builder, ctx.clone(), a_ctx.clone(), field)?;
512 &mut builder,
513 field_ctx.clone(),
514 value_ctx.clone(),
515 field,
516 )?;
517 }553 }
518 if !members.asserts.is_empty() {554 if let Some(asserts_block) = &members.asserts {
519 builder.assert(evaluate_object_assertions_static(555 builder.assert(evaluate_object_assertions_static(
520 value_ctx,556 a_ctx,
521 members.asserts.clone(),557 asserts_block.clone(),
522 ));558 ));
523 }559 }
524 }560 }
529pub fn evaluate_assert(ctx: Context, assertion: &LAssertStmt) -> Result<()> {565pub fn evaluate_assert(ctx: Context, assertion: &LAssertStmt) -> Result<()> {
530 let LAssertStmt { cond, message } = assertion;566 let LAssertStmt { cond, message } = assertion;
531 let assertion_result = in_frame(567 let assertion_result = in_frame(
532 CallLocation::native(),568 CallLocation::new(&cond.span),
533 || "assertion condition".to_owned(),569 || "assertion condition".to_owned(),
534 || bool::from_untyped(evaluate(ctx.clone(), cond)?),570 || bool::from_untyped(evaluate(ctx.clone(), cond)?),
535 )?;571 )?;
550586
551fn evaluate_object_assertions_unbound<B: Unbound<Bound = Context>>(587fn evaluate_object_assertions_unbound<B: Unbound<Bound = Context>>(
552 uctx: B,588 uctx: B,
553 asserts: Rc<Vec<LAssertStmt>>,589 asserts: Rc<LObjAsserts>,
554) -> impl ObjectAssertion {590) -> impl ObjectAssertion {
555 #[derive(Trace)]591 #[derive(Trace)]
556 struct ObjectAssert<B: Trace> {592 struct ObjectAssert<B: Trace> {
557 uctx: B,593 uctx: B,
558 asserts: Rc<Vec<LAssertStmt>>,594 asserts: Rc<LObjAsserts>,
559 }595 }
560 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {596 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {
561 fn run(&self, sup_this: SupThis) -> Result<()> {597 fn run(&self, sup_this: SupThis) -> Result<()> {
562 let ctx = self.uctx.bind(sup_this)?;598 let a_ctx = self.uctx.bind(sup_this)?;
599 let assert_env = Context::enter_using(&a_ctx, &self.asserts.shape);
563 for assert in &*self.asserts {600 for assert in &self.asserts.asserts {
564 evaluate_assert(ctx.clone(), assert)?;601 evaluate_assert(assert_env.clone(), assert)?;
565 }602 }
566 Ok(())603 Ok(())
567 }604 }
568 }605 }
569 ObjectAssert { uctx, asserts }606 ObjectAssert { uctx, asserts }
570}607}
571fn evaluate_object_assertions_static(608fn evaluate_object_assertions_static(
572 ctx: Context,609 a_ctx: Context,
573 asserts: Rc<Vec<LAssertStmt>>,610 asserts: Rc<LObjAsserts>,
574) -> impl ObjectAssertion {611) -> impl ObjectAssertion {
575 #[derive(Trace)]612 #[derive(Trace)]
576 struct ObjectAssert {613 struct ObjectAssert {
577 ctx: Context,614 assert_env: Context,
578 asserts: Rc<Vec<LAssertStmt>>,615 asserts: Rc<LObjAsserts>,
579 }616 }
580 impl ObjectAssertion for ObjectAssert {617 impl ObjectAssertion for ObjectAssert {
581 fn run(&self, _sup_this: SupThis) -> Result<()> {618 fn run(&self, _sup_this: SupThis) -> Result<()> {
582 for assert in &*self.asserts {619 for assert in &self.asserts.asserts {
583 evaluate_assert(self.ctx.clone(), assert)?;620 evaluate_assert(self.assert_env.clone(), assert)?;
584 }621 }
585 Ok(())622 Ok(())
586 }623 }
587 }624 }
625 let assert_env = Context::enter_using(&a_ctx, &asserts.shape);
588 ObjectAssert { ctx, asserts }626 ObjectAssert {
627 assert_env,
628 asserts,
629 }
589}630}
590631
modifiedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
11 prepared::{PreparedCall, parse_prepared_builtin_call},11 prepared::{PreparedCall, parse_prepared_builtin_call},
12};12};
13use crate::{13use crate::{
14 Context, ContextBuilder, Result, Thunk, Val,14 PackedContextSupThis, Result, Thunk, Val,
15 analyze::{LDestruct, LExpr, LFunction},15 analyze::LFunction,
16 evaluate::{destructure::destruct, ensure_sufficient_stack, evaluate, evaluate_trivial},16 evaluate::{
17 destructure::{build_b_thunk, destruct},
18 ensure_sufficient_stack, evaluate, evaluate_trivial,
19 },
17 function::builtin::BuiltinFunc,20 function::builtin::BuiltinFunc,
18};21};
56 /// { a() = ... }59 /// { a() = ... }
57 /// ```60 /// ```
58 pub name: IStr,61 pub name: IStr,
59 /// Context, in which this function was evaluated.
60 ///
61 /// # Example
62 /// In
63 /// ```jsonnet
64 /// local a = 2;
65 /// function() ...
66 /// ```
67 /// context will contain `a`.
68 pub ctx: Context,62 pub(crate) body_captures: PackedContextSupThis,
6963
70 #[educe(PartialEq(method = Rc::ptr_eq))]64 #[educe(PartialEq(method = Rc::ptr_eq))]
71 pub func: Rc<LFunction>,65 pub func: Rc<LFunction>,
82 named: &[Thunk<Val>],76 named: &[Thunk<Val>],
83 prepared: &PreparedCall,77 prepared: &PreparedCall,
84 ) -> Result<Val> {78 ) -> Result<Val> {
85 let has_defaults = !prepared.defaults().is_empty();79 let body_ctx = self.body_captures.clone().enter(|fill, ctx| {
86 let mut builder = ContextBuilder::extend(self.ctx.clone(), self.func.params.len());80 // Place each provided arg-thunk into its destructured slots.
87
88 let fctx = Context::new_future();
89 for (param_idx, thunk) in unnamed.iter().enumerate() {81 for (param_idx, thunk) in unnamed.iter().enumerate() {
90 destruct(82 destruct(
91 &self.func.params[param_idx].destruct,83 &self.func.params[param_idx].destruct,
84 fill,
92 thunk.clone(),85 thunk.clone(),
93 fctx.clone(),
94 &mut builder,86 &ctx,
95 );87 );
96 }88 }
97
98 for &(param_idx, arg_idx) in prepared.named() {89 for &(param_idx, arg_idx) in prepared.named() {
99 destruct(90 destruct(
100 &self.func.params[param_idx].destruct,91 &self.func.params[param_idx].destruct,
92 fill,
101 named[arg_idx].clone(),93 named[arg_idx].clone(),
102 fctx.clone(),
103 &mut builder,94 &ctx,
104 );95 );
105 }96 }
10697
107 if has_defaults {
108 for &param_idx in prepared.defaults() {98 for &param_idx in prepared.defaults() {
109 let param = &self.func.params[param_idx];99 let param = &self.func.params[param_idx];
110 if let Some(default_expr) = &param.default {100 let (shape, expr) = param.default.as_ref().expect("default exists");
111 let default_expr = default_expr.clone();
112 let fctxc = fctx.clone();
113 let thunk = Thunk!(move || {101 let thunk = build_b_thunk(&ctx, shape, expr.clone());
114 let ctx = fctxc.unwrap();
115 evaluate(ctx, &default_expr)
116 });
117 destruct(&param.destruct, thunk, fctx.clone(), &mut builder);102 destruct(&param.destruct, fill, thunk, &ctx);
118 }
119 }103 }
120 };
121 let ctx = builder.build().into_future(fctx);104 });
105
122 ensure_sufficient_stack(|| evaluate(ctx, &self.func.body))106 ensure_sufficient_stack(|| evaluate(body_ctx, &self.func.body))
123 }107 }
124108
125 pub fn evaluate_trivial(&self) -> Option<Val> {109 pub fn evaluate_trivial(&self) -> Option<Val> {
157 Self::Builtin(BuiltinFunc::new(builtin))141 Self::Builtin(BuiltinFunc::new(builtin))
158 }142 }
143
144 pub fn identity() -> Self {
145 Self::builtin(builtin_id {})
146 }
159147
160 pub fn params(&self) -> FunctionSignature {148 pub fn params(&self) -> FunctionSignature {
161 match self {149 match self {
193 }181 }
194182
195 /// Is this function an identity function.183 /// Is this function an identity function.
196 ///
197 /// Currently only works for builtin `std.id`, aka `Self::Id` value, and `function(x) x`.
198 ///184 ///
199 /// This function should only be used for optimization, not for the conditional logic, i.e code should work with syntetic identity function too185 /// This function should only be used for optimization, not for the conditional logic, i.e code should work with syntetic identity function too
200 pub fn is_identity(&self) -> bool {186 pub fn is_identity(&self) -> bool {
201 match self {187 match self {
202 Self::Builtin(b) => b.as_any().downcast_ref::<builtin_id>().is_some(),188 Self::Builtin(b) => b.as_any().downcast_ref::<builtin_id>().is_some(),
203 Self::Normal(desc) => {189 Self::Normal(_) => false,
204 if desc.func.params.len() != 1 {
205 return false;
206 }
207 let param = &desc.func.params[0];
208 if param.default.is_some() {
209 return false;
210 }
211 #[allow(irrefutable_let_patterns, reason = "refutable with exp-destruct")]
212 let LDestruct::Full(id) = &param.destruct else {
213 return false;
214 };
215 matches!(&*desc.func.body, LExpr::Local(v) if v == id)
216 }
217 }190 }
218 }191 }
219192
modifiedcrates/jrsonnet-evaluator/src/function/prepared.rsdiffbeforeafterboth
46 pub fn defaults(&self) -> &[usize] {46 pub fn defaults(&self) -> &[usize] {
47 &self.defaults47 &self.defaults
48 }48 }
49 pub const fn empty() -> Self {
50 Self {
51 named: Vec::new(),
52 defaults: Vec::new(),
53 }
54 }
49}55}
5056
51pub fn prepare_call(57pub fn prepare_call(
modifiedcrates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth
12};12};
1313
14use crate::{14use crate::{
15 Error as JrError, ObjValue, ObjValueBuilder, Result, Val, in_description_frame, runtime_error,15 Error as JrError, ObjValue, ObjValueBuilder, Result, Val, error, in_description_frame,
16};16};
1717
18impl<'de> Deserialize<'de> for Val {18impl<'de> Deserialize<'de> for Val {
629 where629 where
630 T: std::fmt::Display,630 T: std::fmt::Display,
631 {631 {
632 runtime_error!("serde: {msg}")632 error!("serde: {msg}")
633 }633 }
634}634}
635635
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
58pub use val::{Thunk, Val};58pub use val::{Thunk, Val};
5959
60pub mod analyze;60pub mod analyze;
61use self::analyze::{LExpr, analyze_root};
61use crate::gc::WithCapacityExt as _;62use crate::gc::WithCapacityExt as _;
6263
63#[allow(clippy::needless_return)]64#[allow(clippy::needless_return)]
408 file.evaluating = true;409 file.evaluating = true;
409 // Dropping file cache guard here, as evaluation may use this map too410 // Dropping file cache guard here, as evaluation may use this map too
410 drop(file_cache);411 drop(file_cache);
411 let (ctx, externals) = self.create_default_context(file_name.clone()).build();412 let (externals, thunks) = self.create_default_context(file_name).build();
412 let report = analyze::analyze_root(&parsed, externals);413 let report = analyze_root(&parsed, externals);
413 if report.errored {414 if report.errored {
414 return Err(StaticAnalysisError(report.diagnostics_list).into());415 return Err(StaticAnalysisError(report.diagnostics_list).into());
415 }416 }
417 debug_assert_eq!(report.root_shape.n_locals as usize, thunks.len());
418 debug_assert!(report.root_shape.captures.is_empty());
419 let ctx = Context::root(thunks);
416 let res = evaluate::evaluate(ctx.build(), &report.lir);420 let res = evaluate::evaluate(ctx, &report.lir);
417421
418 let mut file_cache = self.file_cache();422 let mut file_cache = self.file_cache();
419 let mut file = file_cache.entry(path);423 let mut file = file_cache.entry(path);
501 }505 }
502}506}
507
508pub struct PreparedSnippet {
509 lir: LExpr,
510 thunks: Vec<Thunk<Val>>,
511}
503512
504/// Raw methods evaluate passed values but don't perform TLA execution513/// Raw methods evaluate passed values but don't perform TLA execution
505impl State {514impl State {
515 /// Parses and analyses the given snippet with a custom context
516 /// modifier.
517 pub fn prepare_snippet_with(
518 &self,
519 name: impl Into<IStr>,
520 code: impl Into<IStr>,
521 context_initializer: &dyn ContextInitializer,
522 ) -> Result<PreparedSnippet> {
523 let code = code.into();
524 let source = Source::new_virtual(name.into(), code.clone());
525 let parsed = parse_jsonnet(&code, source.clone()).map_err(|e| ImportSyntaxError {
526 path: source.clone(),
527 error: Box::new(e),
528 })?;
529 let (externals, thunks) = self
530 .create_default_context_with(source, context_initializer)
531 .build();
532 let report = analyze_root(&parsed, externals);
533 if report.errored {
534 return Err(StaticAnalysisError(report.diagnostics_list).into());
535 }
536 debug_assert_eq!(report.root_shape.n_locals as usize, thunks.len());
537 debug_assert!(report.root_shape.captures.is_empty());
538 Ok(PreparedSnippet {
539 lir: report.lir,
540 thunks,
541 })
542 }
543 /// Parses and analyses the given snippet
544 pub fn prepare_snippet(
545 &self,
546 name: impl Into<IStr>,
547 code: impl Into<IStr>,
548 ) -> Result<PreparedSnippet> {
549 self.prepare_snippet_with(name, code, &())
550 }
551 pub fn evaluate_prepared_snippet(&self, prepared: &PreparedSnippet) -> Result<Val> {
552 let ctx = Context::root(prepared.thunks.clone());
553 evaluate::evaluate(ctx, &prepared.lir)
554 }
555 /// Parses and evaluates the given snippet with custom context modifier
556 pub fn evaluate_snippet_with(
557 &self,
558 name: impl Into<IStr>,
559 code: impl Into<IStr>,
560 context_initializer: &dyn ContextInitializer,
561 ) -> Result<Val> {
562 let prepared = self.prepare_snippet_with(name, code, context_initializer)?;
563 self.evaluate_prepared_snippet(&prepared)
564 }
506 /// Parses and evaluates the given snippet565 /// Parses and evaluates the given snippet
507 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {566 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {
508 self.evaluate_snippet_with(name, code, &())567 self.evaluate_snippet_with(name, code, &())
509 }568 }
510 /// Parses and evaluates the given snippet with custom context modifier
511 pub fn evaluate_snippet_with(
512 &self,
513 name: impl Into<IStr>,
514 code: impl Into<IStr>,
515 context_initializer: &dyn ContextInitializer,
516 ) -> Result<Val> {
517 let code = code.into();
518 let source = Source::new_virtual(name.into(), code.clone());
519 let parsed = parse_jsonnet(&code, source.clone()).map_err(|e| ImportSyntaxError {
520 path: source.clone(),
521 error: Box::new(e),
522 })?;
523 let (ctx, externals) = self
524 .create_default_context_with(source.clone(), context_initializer)
525 .build();
526 let report = analyze::analyze_root(&parsed, externals);
527 if report.errored {
528 return Err(StaticAnalysisError(report.diagnostics_list).into());
529 }
530 evaluate::evaluate(ctx.build(), &report.lir)
531 }
532}569}
533570
534/// Settings utilities571/// Settings utilities
modifiedtests/benches/cpp_test_suite.rsdiffbeforeafterboth
1use std::{collections::HashMap, fs::read_dir, hint::black_box, path::Path};1use std::{collections::HashMap, fs, fs::read_dir, hint::black_box, path::Path};
22
3use criterion::{Criterion, criterion_group, criterion_main};3use criterion::{Criterion, criterion_group, criterion_main};
4use jrsonnet_evaluator::{4use jrsonnet_evaluator::{
5 FileImportResolver, State, apply_tla,5 FileImportResolver, State, apply_tla, manifest::JsonFormat, stack::limit_stack_depth,
6 manifest::{BlackBoxFormat, JsonFormat},
7 stack::limit_stack_depth,
8 trace::PathResolver,6 trace::PathResolver,
9};7};
12static GLOBAL: mimallocator::Mimalloc = mimallocator::Mimalloc;10static GLOBAL: mimallocator::Mimalloc = mimallocator::Mimalloc;
1311
14fn bench_entry(c: &mut Criterion, path: &Path) {12fn bench_entry(c: &mut Criterion, path: &Path) {
13 let name = path
14 .file_name()
15 .expect("file path")
16 .to_str()
17 .expect("name is utf-8")
18 .to_owned();
19 let code = fs::read_to_string(path).expect("read bench source");
20
15 c.bench_function(21 c.bench_function(&name, |b| {
16 path.file_name()
17 .expect("file path")
18 .to_str()
28 .import_resolver(FileImportResolver::new(vec![]));28 .import_resolver(FileImportResolver::new(vec![]));
29
30 let s = s.build();29 let s = s.build();
31 let _s = s.enter();30 let _entered = s.enter();
31
32 // Parse + analysis happen once; each iter only measures
33 // evaluation + manifestation.
34 let prepared = s
35 .prepare_snippet(name.clone(), code.clone())
36 .expect("prepared");
3237
33 b.iter(|| {38 b.iter(|| {
34 let imported = s.import(path).expect("evaluated");39 let imported = s.evaluate_prepared_snippet(&prepared).expect("evaluated");
35 let res = apply_tla(&HashMap::new(), imported).expect("tla applied");40 let res = apply_tla(&HashMap::new(), imported).expect("tla applied");
36 black_box(res.manifest(JsonFormat::cli(3)).expect("manifested"));41 black_box(res.manifest(JsonFormat::cli(3)).expect("manifested"));
37 });42 });