12#![cfg_attr(nightly, feature(thread_local, type_alias_impl_trait))]345extern crate self as jrsonnet_evaluator;67mod arr;8pub mod async_import;9mod ctx;10mod dynamic;11pub mod error;12mod evaluate;13pub mod function;14pub mod gc;15mod import;16mod integrations;17pub mod manifest;18mod map;19mod obj;20pub mod stack;21pub mod stdlib;22mod tla;23pub mod trace;24pub mod typed;25pub mod val;2627use std::{28 any::Any,29 cell::{RefCell, RefMut},30 clone::Clone,31 collections::hash_map::Entry,32 fmt::{self, Debug},33 marker::PhantomData,34 rc::Rc,35};3637pub use ctx::*;38pub use dynamic::*;39pub use error::{Error, ErrorKind::*, Result, ResultExt};40pub use evaluate::*;41use function::CallLocation;42pub use import::*;43use jrsonnet_gcmodule::{cc_dyn, Cc, Trace};44pub use jrsonnet_interner::{IBytes, IStr};45#[doc(hidden)]46pub use jrsonnet_macros;47pub use jrsonnet_parser as parser;48use jrsonnet_parser::{Expr, ParserSettings, Source, SourcePath, Spanned};49pub use obj::*;50pub use rustc_hash;51use rustc_hash::FxHashMap;52use stack::check_depth;53pub use tla::apply_tla;54pub use val::{Thunk, Val};5556use crate::gc::WithCapacityExt as _;5758cc_dyn!(59 #[derive(Clone)]60 CcUnbound<V>,61 Unbound<Bound = V>62);63646566pub trait Unbound: Trace {67 68 type Bound;69 70 fn bind(&self, sup_this: SupThis) -> Result<Self::Bound>;71}72737475#[derive(Clone, Trace)]76pub enum MaybeUnbound {77 78 Unbound(CcUnbound<Val>),79 80 Bound(Thunk<Val>),81}8283impl Debug for MaybeUnbound {84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {85 write!(f, "MaybeUnbound")86 }87}88impl MaybeUnbound {89 90 pub fn evaluate(&self, sup_this: SupThis) -> Result<Val> {91 match self {92 Self::Unbound(v) => v.0.bind(sup_this),93 Self::Bound(v) => Ok(v.evaluate()?),94 }95 }96}9798cc_dyn!(CcContextInitializer, ContextInitializer);99100101102pub trait ContextInitializer: Trace {103 104 fn reserve_vars(&self) -> usize {105 0106 }107 108 109 110 fn initialize(&self, for_file: Source) -> Context {111 let mut builder = ContextBuilder::with_capacity(self.reserve_vars());112 self.populate(for_file, &mut builder);113 builder.build()114 }115 116 117 fn populate(&self, for_file: Source, builder: &mut ContextBuilder);118 119 120 fn as_any(&self) -> &dyn Any;121}122123124impl ContextInitializer for () {125 fn populate(&self, _for_file: Source, _builder: &mut ContextBuilder) {}126 fn as_any(&self) -> &dyn Any {127 self128 }129}130131impl<T> ContextInitializer for Option<T>132where133 T: ContextInitializer,134{135 fn initialize(&self, for_file: Source) -> Context {136 if let Some(ctx) = self {137 ctx.initialize(for_file)138 } else {139 ().initialize(for_file)140 }141 }142143 fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {144 if let Some(ctx) = self {145 ctx.populate(for_file, builder);146 }147 }148149 fn as_any(&self) -> &dyn Any {150 self151 }152}153154macro_rules! impl_context_initializer {155 ($($gen:ident)*) => {156 #[allow(non_snake_case)]157 impl<$($gen: ContextInitializer + Trace,)*> ContextInitializer for ($($gen,)*) {158 fn reserve_vars(&self) -> usize {159 let mut out = 0;160 let ($($gen,)*) = self;161 $(out += $gen.reserve_vars();)*162 out163 }164 fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {165 let ($($gen,)*) = self;166 $($gen.populate(for_file.clone(), builder);)*167 }168 fn as_any(&self) -> &dyn Any {169 self170 }171 }172 };173 ($($cur:ident)* @ $c:ident $($rest:ident)*) => {174 impl_context_initializer!($($cur)*);175 impl_context_initializer!($($cur)* $c @ $($rest)*);176 };177 ($($cur:ident)* @) => {178 impl_context_initializer!($($cur)*);179 }180}181impl_context_initializer! {182 A @ B C D E F G183}184185#[derive(Trace)]186struct FileData {187 string: Option<IStr>,188 bytes: Option<IBytes>,189 parsed: Option<Rc<Spanned<Expr>>>,190 evaluated: Option<Val>,191192 evaluating: bool,193}194impl FileData {195 fn new_string(data: IStr) -> Self {196 Self {197 string: Some(data),198 bytes: None,199 parsed: None,200 evaluated: None,201 evaluating: false,202 }203 }204 fn new_bytes(data: IBytes) -> Self {205 Self {206 string: None,207 bytes: Some(data),208 parsed: None,209 evaluated: None,210 evaluating: false,211 }212 }213 pub(crate) fn get_string(&mut self) -> Option<IStr> {214 if self.string.is_none() {215 self.string = Some(216 self.bytes217 .as_ref()218 .expect("either string or bytes should be set")219 .clone()220 .cast_str()?,221 );222 }223 Some(self.string.clone().expect("just set"))224 }225}226227#[derive(Trace)]228pub struct EvaluationStateInternals {229 230 file_cache: RefCell<FxHashMap<SourcePath, FileData>>,231 232 233 context_initializer: CcContextInitializer,234 235 import_resolver: Rc<dyn ImportResolver>,236}237238239#[derive(Clone, Trace)]240pub struct State(Cc<EvaluationStateInternals>);241242thread_local! {243 pub static DEFAULT_STATE: State = State::builder().build();244 pub static STATE: RefCell<Option<State>> = const {RefCell::new(None)};245}246pub struct StateEnterGuard(PhantomData<()>);247impl Drop for StateEnterGuard {248 fn drop(&mut self) {249 STATE.with_borrow_mut(|v| *v = None);250 }251}252253pub fn with_state<V>(v: impl FnOnce(State) -> V) -> V {254 if let Some(state) = STATE.with_borrow(Clone::clone) {255 v(state)256 } else {257 let s = DEFAULT_STATE.with(Clone::clone);258 v(s)259 }260}261262impl State {263 pub fn enter(&self) -> StateEnterGuard {264 self.try_enter().expect("entered state already exists")265 }266 pub fn try_enter(&self) -> Option<StateEnterGuard> {267 STATE.with_borrow_mut(|v| {268 if v.is_none() {269 *v = Some(self.clone());270 Some(StateEnterGuard(PhantomData))271 } else {272 None273 }274 })275 }276 277 pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {278 let mut file_cache = self.file_cache();279 let mut file = file_cache.entry(path.clone());280281 let file = match file {282 Entry::Occupied(ref mut d) => d.get_mut(),283 Entry::Vacant(v) => {284 let data = self.import_resolver().load_file_contents(&path)?;285 v.insert(FileData::new_string(286 std::str::from_utf8(&data)287 .map_err(|_| ImportBadFileUtf8(path.clone()))?288 .into(),289 ))290 }291 };292 Ok(file293 .get_string()294 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?)295 }296 297 pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {298 let mut file_cache = self.file_cache();299 let mut file = file_cache.entry(path.clone());300301 let file = match file {302 Entry::Occupied(ref mut d) => d.get_mut(),303 Entry::Vacant(v) => {304 let data = self.import_resolver().load_file_contents(&path)?;305 v.insert(FileData::new_bytes(data.as_slice().into()))306 }307 };308 if let Some(str) = &file.bytes {309 return Ok(str.clone());310 }311 if file.bytes.is_none() {312 file.bytes = Some(313 file.string314 .as_ref()315 .expect("either string or bytes should be set")316 .clone()317 .cast_bytes(),318 );319 }320 Ok(file.bytes.as_ref().expect("just set").clone())321 }322 323 pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {324 let mut file_cache = self.file_cache();325 let mut file = file_cache.entry(path.clone());326327 let file = match file {328 Entry::Occupied(ref mut d) => d.get_mut(),329 Entry::Vacant(v) => {330 let data = self.import_resolver().load_file_contents(&path)?;331 v.insert(FileData::new_string(332 std::str::from_utf8(&data)333 .map_err(|_| ImportBadFileUtf8(path.clone()))?334 .into(),335 ))336 }337 };338 if let Some(val) = &file.evaluated {339 return Ok(val.clone());340 }341 let code = file342 .get_string()343 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?;344 let file_name = Source::new(path.clone(), code.clone());345 if file.parsed.is_none() {346 file.parsed = Some(347 jrsonnet_parser::parse(348 &code,349 &ParserSettings {350 source: file_name.clone(),351 },352 )353 .map(Rc::new)354 .map_err(|e| ImportSyntaxError {355 path: file_name.clone(),356 error: Box::new(e),357 })?,358 );359 }360 let parsed = file.parsed.as_ref().expect("just set").clone();361 if file.evaluating {362 bail!(InfiniteRecursionDetected)363 }364 file.evaluating = true;365 366 drop(file_cache);367 let res = evaluate(self.create_default_context(file_name), &parsed);368369 let mut file_cache = self.file_cache();370 let mut file = file_cache.entry(path.clone());371372 let Entry::Occupied(file) = &mut file else {373 unreachable!("this file was just here")374 };375 let file = file.get_mut();376 file.evaluating = false;377 match res {378 Ok(v) => {379 file.evaluated = Some(v.clone());380 Ok(v)381 }382 Err(e) => Err(e),383 }384 }385386 387 pub fn import_from(&self, from: &SourcePath, path: impl AsPathLike) -> Result<Val> {388 let resolved = self.resolve_from(from, &path)?;389 self.import_resolved(resolved)390 }391 pub fn import(&self, path: impl AsPathLike) -> Result<Val> {392 let resolved = self.resolve_from_default(&path)?;393 self.import_resolved(resolved)394 }395396 397 pub fn create_default_context(&self, source: Source) -> Context {398 self.context_initializer().initialize(source)399 }400401 402 pub fn create_default_context_with(403 &self,404 source: Source,405 context_initializer: impl ContextInitializer,406 ) -> Context {407 let default_initializer = self.context_initializer();408 let mut builder = ContextBuilder::with_capacity(409 default_initializer.reserve_vars() + context_initializer.reserve_vars(),410 );411 default_initializer.populate(source.clone(), &mut builder);412 context_initializer.populate(source, &mut builder);413414 builder.build()415 }416}417418419impl State {420 fn file_cache(&self) -> RefMut<'_, FxHashMap<SourcePath, FileData>> {421 self.0.file_cache.borrow_mut()422 }423}424425pub fn in_frame<T>(426 e: CallLocation<'_>,427 frame_desc: impl FnOnce() -> String,428 f: impl FnOnce() -> Result<T>,429) -> Result<T> {430 let _guard = check_depth()?;431432 f().with_description_src(e, frame_desc)433}434435436pub fn in_description_frame<T>(437 frame_desc: impl FnOnce() -> String,438 f: impl FnOnce() -> Result<T>,439) -> Result<T> {440 let _guard = check_depth()?;441442 f().with_description(frame_desc)443}444445#[derive(Trace)]446pub struct InitialUnderscore(pub Thunk<Val>);447impl ContextInitializer for InitialUnderscore {448 fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {449 builder.bind("_", self.0.clone());450 }451452 fn as_any(&self) -> &dyn Any {453 self454 }455}456457458impl State {459 460 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {461 let code = code.into();462 let source = Source::new_virtual(name.into(), code.clone());463 let parsed = jrsonnet_parser::parse(464 &code,465 &ParserSettings {466 source: source.clone(),467 },468 )469 .map_err(|e| ImportSyntaxError {470 path: source.clone(),471 error: Box::new(e),472 })?;473 evaluate(self.create_default_context(source), &parsed)474 }475 476 pub fn evaluate_snippet_with(477 &self,478 name: impl Into<IStr>,479 code: impl Into<IStr>,480 context_initializer: impl ContextInitializer,481 ) -> Result<Val> {482 let code = code.into();483 let source = Source::new_virtual(name.into(), code.clone());484 let parsed = jrsonnet_parser::parse(485 &code,486 &ParserSettings {487 source: source.clone(),488 },489 )490 .map_err(|e| ImportSyntaxError {491 path: source.clone(),492 error: Box::new(e),493 })?;494 evaluate(495 self.create_default_context_with(source, context_initializer),496 &parsed,497 )498 }499}500501502impl State {503 504 #[allow(clippy::missing_panics_doc)]505 pub fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result<SourcePath> {506 self.import_resolver().resolve_from(from, path)507 }508 #[allow(clippy::missing_panics_doc)]509 pub fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result<SourcePath> {510 self.import_resolver().resolve_from_default(path)511 }512 pub fn import_resolver(&self) -> &dyn ImportResolver {513 &*self.0.import_resolver514 }515 pub fn context_initializer(&self) -> &dyn ContextInitializer {516 &*self.0.context_initializer.0517 }518}519520impl State {521 pub fn builder() -> StateBuilder {522 StateBuilder::default()523 }524}525526impl Default for State {527 fn default() -> Self {528 Self::builder().build()529 }530}531532#[derive(Default)]533pub struct StateBuilder {534 import_resolver: Option<Rc<dyn ImportResolver>>,535 context_initializer: Option<CcContextInitializer>,536}537impl StateBuilder {538 pub fn import_resolver(&mut self, import_resolver: impl ImportResolver) -> &mut Self {539 let _ = self.import_resolver.insert(Rc::new(import_resolver));540 self541 }542 pub fn context_initializer(543 &mut self,544 context_initializer: impl ContextInitializer,545 ) -> &mut Self {546 let _ = self547 .context_initializer548 .insert(CcContextInitializer::new(context_initializer));549 self550 }551 pub fn build(mut self) -> State {552 State(Cc::new(EvaluationStateInternals {553 file_cache: RefCell::new(FxHashMap::new()),554 context_initializer: self555 .context_initializer556 .take()557 .unwrap_or_else(|| CcContextInitializer::new(())),558 import_resolver: self559 .import_resolver560 .take()561 .unwrap_or_else(|| Rc::new(DummyImportResolver)),562 }))563 }564}