1#![cfg_attr(feature = "nightly", feature(thread_local, type_alias_impl_trait))]
3
4extern crate self as jrsonnet_evaluator;
6
7mod arr;
8#[cfg(feature = "async-import")]
9pub mod async_import;
10mod ctx;
11mod dynamic;
12pub mod error;
13mod evaluate;
14pub mod function;
15pub mod gc;
16mod import;
17mod integrations;
18pub mod manifest;
19mod map;
20mod obj;
21pub mod stack;
22pub mod stdlib;
23mod tla;
24pub mod trace;
25pub mod typed;
26pub mod val;
27
28use std::{
29 any::Any,
30 cell::{RefCell, RefMut},
31 fmt::{self, Debug},
32 path::Path,
33};
34
35pub use ctx::*;
36pub use dynamic::*;
37pub use error::{Error, ErrorKind::*, Result, ResultExt};
38pub use evaluate::*;
39use function::CallLocation;
40use gc::{GcHashMap, TraceBox};
41use hashbrown::hash_map::RawEntryMut;
42pub use import::*;
43use jrsonnet_gcmodule::{Cc, Trace};
44pub use jrsonnet_interner::{IBytes, IStr};
45#[doc(hidden)]
46pub use jrsonnet_macros;
47pub use jrsonnet_parser as parser;
48use jrsonnet_parser::{LocExpr, ParserSettings, Source, SourcePath};
49pub use obj::*;
50use stack::check_depth;
51pub use tla::apply_tla;
52pub use val::{Thunk, Val};
53
54pub trait Unbound: Trace {
57 type Bound;
59 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;
61}
62
63#[derive(Clone, Trace)]
66pub enum MaybeUnbound {
67 Unbound(Cc<TraceBox<dyn Unbound<Bound = Val>>>),
69 Bound(Thunk<Val>),
71}
72
73impl Debug for MaybeUnbound {
74 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75 write!(f, "MaybeUnbound")
76 }
77}
78impl MaybeUnbound {
79 pub fn evaluate(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {
81 match self {
82 Self::Unbound(v) => v.bind(sup, this),
83 Self::Bound(v) => Ok(v.evaluate()?),
84 }
85 }
86}
87
88pub trait ContextInitializer: Trace {
91 fn reserve_vars(&self) -> usize {
93 0
94 }
95 fn initialize(&self, state: State, for_file: Source) -> Context {
99 let mut builder = ContextBuilder::with_capacity(state, self.reserve_vars());
100 self.populate(for_file, &mut builder);
101 builder.build()
102 }
103 fn populate(&self, for_file: Source, builder: &mut ContextBuilder);
106 fn as_any(&self) -> &dyn Any;
109}
110
111impl ContextInitializer for () {
113 fn populate(&self, _for_file: Source, _builder: &mut ContextBuilder) {}
114 fn as_any(&self) -> &dyn Any {
115 self
116 }
117}
118
119impl<T> ContextInitializer for Option<T>
120where
121 T: ContextInitializer,
122{
123 fn initialize(&self, state: State, for_file: Source) -> Context {
124 if let Some(ctx) = self {
125 ctx.initialize(state, for_file)
126 } else {
127 ().initialize(state, for_file)
128 }
129 }
130
131 fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {
132 if let Some(ctx) = self {
133 ctx.populate(for_file, builder);
134 }
135 }
136
137 fn as_any(&self) -> &dyn Any {
138 self
139 }
140}
141
142macro_rules! impl_context_initializer {
143 ($($gen:ident)*) => {
144 #[allow(non_snake_case)]
145 impl<$($gen: ContextInitializer + Trace,)*> ContextInitializer for ($($gen,)*) {
146 fn reserve_vars(&self) -> usize {
147 let mut out = 0;
148 let ($($gen,)*) = self;
149 $(out += $gen.reserve_vars();)*
150 out
151 }
152 fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {
153 let ($($gen,)*) = self;
154 $($gen.populate(for_file.clone(), builder);)*
155 }
156 fn as_any(&self) -> &dyn Any {
157 self
158 }
159 }
160 };
161 ($($cur:ident)* @ $c:ident $($rest:ident)*) => {
162 impl_context_initializer!($($cur)*);
163 impl_context_initializer!($($cur)* $c @ $($rest)*);
164 };
165 ($($cur:ident)* @) => {
166 impl_context_initializer!($($cur)*);
167 }
168}
169impl_context_initializer! {
170 A @ B C D E F G
171}
172
173#[derive(Trace)]
174struct FileData {
175 string: Option<IStr>,
176 bytes: Option<IBytes>,
177 parsed: Option<LocExpr>,
178 evaluated: Option<Val>,
179
180 evaluating: bool,
181}
182impl FileData {
183 fn new_string(data: IStr) -> Self {
184 Self {
185 string: Some(data),
186 bytes: None,
187 parsed: None,
188 evaluated: None,
189 evaluating: false,
190 }
191 }
192 fn new_bytes(data: IBytes) -> Self {
193 Self {
194 string: None,
195 bytes: Some(data),
196 parsed: None,
197 evaluated: None,
198 evaluating: false,
199 }
200 }
201 pub(crate) fn get_string(&mut self) -> Option<IStr> {
202 if self.string.is_none() {
203 self.string = Some(
204 self.bytes
205 .as_ref()
206 .expect("either string or bytes should be set")
207 .clone()
208 .cast_str()?,
209 );
210 }
211 Some(self.string.clone().expect("just set"))
212 }
213}
214
215#[derive(Trace)]
216pub struct EvaluationStateInternals {
217 file_cache: RefCell<GcHashMap<SourcePath, FileData>>,
219 context_initializer: TraceBox<dyn ContextInitializer>,
222 import_resolver: TraceBox<dyn ImportResolver>,
224}
225
226#[derive(Clone, Trace)]
228pub struct State(Cc<EvaluationStateInternals>);
229
230impl State {
231 pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {
233 let mut file_cache = self.file_cache();
234 let mut file = file_cache.raw_entry_mut().from_key(&path);
235
236 let file = match file {
237 RawEntryMut::Occupied(ref mut d) => d.get_mut(),
238 RawEntryMut::Vacant(v) => {
239 let data = self.import_resolver().load_file_contents(&path)?;
240 v.insert(
241 path.clone(),
242 FileData::new_string(
243 std::str::from_utf8(&data)
244 .map_err(|_| ImportBadFileUtf8(path.clone()))?
245 .into(),
246 ),
247 )
248 .1
249 }
250 };
251 Ok(file
252 .get_string()
253 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?)
254 }
255 pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {
257 let mut file_cache = self.file_cache();
258 let mut file = file_cache.raw_entry_mut().from_key(&path);
259
260 let file = match file {
261 RawEntryMut::Occupied(ref mut d) => d.get_mut(),
262 RawEntryMut::Vacant(v) => {
263 let data = self.import_resolver().load_file_contents(&path)?;
264 v.insert(path.clone(), FileData::new_bytes(data.as_slice().into()))
265 .1
266 }
267 };
268 if let Some(str) = &file.bytes {
269 return Ok(str.clone());
270 }
271 if file.bytes.is_none() {
272 file.bytes = Some(
273 file.string
274 .as_ref()
275 .expect("either string or bytes should be set")
276 .clone()
277 .cast_bytes(),
278 );
279 }
280 Ok(file.bytes.as_ref().expect("just set").clone())
281 }
282 pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {
284 let mut file_cache = self.file_cache();
285 let mut file = file_cache.raw_entry_mut().from_key(&path);
286
287 let file = match file {
288 RawEntryMut::Occupied(ref mut d) => d.get_mut(),
289 RawEntryMut::Vacant(v) => {
290 let data = self.import_resolver().load_file_contents(&path)?;
291 v.insert(
292 path.clone(),
293 FileData::new_string(
294 std::str::from_utf8(&data)
295 .map_err(|_| ImportBadFileUtf8(path.clone()))?
296 .into(),
297 ),
298 )
299 .1
300 }
301 };
302 if let Some(val) = &file.evaluated {
303 return Ok(val.clone());
304 }
305 let code = file
306 .get_string()
307 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?;
308 let file_name = Source::new(path.clone(), code.clone());
309 if file.parsed.is_none() {
310 file.parsed = Some(
311 jrsonnet_parser::parse(
312 &code,
313 &ParserSettings {
314 source: file_name.clone(),
315 },
316 )
317 .map_err(|e| ImportSyntaxError {
318 path: file_name.clone(),
319 error: Box::new(e),
320 })?,
321 );
322 }
323 let parsed = file.parsed.as_ref().expect("just set").clone();
324 if file.evaluating {
325 bail!(InfiniteRecursionDetected)
326 }
327 file.evaluating = true;
328 drop(file_cache);
330 let res = evaluate(self.create_default_context(file_name), &parsed);
331
332 let mut file_cache = self.file_cache();
333 let mut file = file_cache.raw_entry_mut().from_key(&path);
334
335 let RawEntryMut::Occupied(file) = &mut file else {
336 unreachable!("this file was just here!")
337 };
338 let file = file.get_mut();
339 file.evaluating = false;
340 match res {
341 Ok(v) => {
342 file.evaluated = Some(v.clone());
343 Ok(v)
344 }
345 Err(e) => Err(e),
346 }
347 }
348
349 pub fn import_from(&self, from: &SourcePath, path: &str) -> Result<Val> {
351 let resolved = self.resolve_from(from, path)?;
352 self.import_resolved(resolved)
353 }
354 pub fn import(&self, path: impl AsRef<Path>) -> Result<Val> {
355 let resolved = self.resolve(path)?;
356 self.import_resolved(resolved)
357 }
358
359 pub fn create_default_context(&self, source: Source) -> Context {
361 self.context_initializer().initialize(self.clone(), source)
362 }
363
364 pub fn create_default_context_with(
366 &self,
367 source: Source,
368 context_initializer: impl ContextInitializer,
369 ) -> Context {
370 let default_initializer = self.context_initializer();
371 let mut builder = ContextBuilder::with_capacity(
372 self.clone(),
373 default_initializer.reserve_vars() + context_initializer.reserve_vars(),
374 );
375 default_initializer.populate(source.clone(), &mut builder);
376 context_initializer.populate(source, &mut builder);
377
378 builder.build()
379 }
380}
381
382impl State {
384 fn file_cache(&self) -> RefMut<'_, GcHashMap<SourcePath, FileData>> {
385 self.0.file_cache.borrow_mut()
386 }
387}
388pub fn in_frame<T>(
390 e: CallLocation<'_>,
391 frame_desc: impl FnOnce() -> String,
392 f: impl FnOnce() -> Result<T>,
393) -> Result<T> {
394 let _guard = check_depth()?;
395
396 f().with_description_src(e, frame_desc)
397}
398
399pub fn in_description_frame<T>(
401 frame_desc: impl FnOnce() -> String,
402 f: impl FnOnce() -> Result<T>,
403) -> Result<T> {
404 let _guard = check_depth()?;
405
406 f().with_description(frame_desc)
407}
408
409#[derive(Trace)]
410pub struct InitialUnderscore(pub Thunk<Val>);
411impl ContextInitializer for InitialUnderscore {
412 fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {
413 builder.bind("_", self.0.clone());
414 }
415
416 fn as_any(&self) -> &dyn Any {
417 self
418 }
419}
420
421impl State {
423 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {
425 let code = code.into();
426 let source = Source::new_virtual(name.into(), code.clone());
427 let parsed = jrsonnet_parser::parse(
428 &code,
429 &ParserSettings {
430 source: source.clone(),
431 },
432 )
433 .map_err(|e| ImportSyntaxError {
434 path: source.clone(),
435 error: Box::new(e),
436 })?;
437 evaluate(self.create_default_context(source), &parsed)
438 }
439 pub fn evaluate_snippet_with(
441 &self,
442 name: impl Into<IStr>,
443 code: impl Into<IStr>,
444 context_initializer: impl ContextInitializer,
445 ) -> Result<Val> {
446 let code = code.into();
447 let source = Source::new_virtual(name.into(), code.clone());
448 let parsed = jrsonnet_parser::parse(
449 &code,
450 &ParserSettings {
451 source: source.clone(),
452 },
453 )
454 .map_err(|e| ImportSyntaxError {
455 path: source.clone(),
456 error: Box::new(e),
457 })?;
458 evaluate(
459 self.create_default_context_with(source, context_initializer),
460 &parsed,
461 )
462 }
463}
464
465impl State {
467 #[allow(clippy::missing_panics_doc)]
469 pub fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {
470 self.import_resolver().resolve_from(from, path.as_ref())
471 }
472
473 #[allow(clippy::missing_panics_doc)]
475 pub fn resolve(&self, path: impl AsRef<Path>) -> Result<SourcePath> {
476 self.import_resolver().resolve(path.as_ref())
477 }
478 pub fn import_resolver(&self) -> &dyn ImportResolver {
479 &*self.0.import_resolver
480 }
481 pub fn context_initializer(&self) -> &dyn ContextInitializer {
482 &*self.0.context_initializer
483 }
484}
485
486impl State {
487 pub fn builder() -> StateBuilder {
488 StateBuilder::default()
489 }
490}
491
492impl Default for State {
493 fn default() -> Self {
494 Self::builder().build()
495 }
496}
497
498#[derive(Default)]
499pub struct StateBuilder {
500 import_resolver: Option<TraceBox<dyn ImportResolver>>,
501 context_initializer: Option<TraceBox<dyn ContextInitializer>>,
502}
503impl StateBuilder {
504 pub fn import_resolver(&mut self, import_resolver: impl ImportResolver) -> &mut Self {
505 let _ = self.import_resolver.insert(tb!(import_resolver));
506 self
507 }
508 pub fn context_initializer(
509 &mut self,
510 context_initializer: impl ContextInitializer,
511 ) -> &mut Self {
512 let _ = self.context_initializer.insert(tb!(context_initializer));
513 self
514 }
515 pub fn build(mut self) -> State {
516 State(Cc::new(EvaluationStateInternals {
517 file_cache: RefCell::new(GcHashMap::new()),
518 context_initializer: self.context_initializer.take().unwrap_or_else(|| tb!(())),
519 import_resolver: self
520 .import_resolver
521 .take()
522 .unwrap_or_else(|| tb!(DummyImportResolver)),
523 }))
524 }
525}