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::{LocExpr, ParserSettings, Source, SourcePath};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<LocExpr>,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_err(|e| ImportSyntaxError {354 path: file_name.clone(),355 error: Box::new(e),356 })?,357 );358 }359 let parsed = file.parsed.as_ref().expect("just set").clone();360 if file.evaluating {361 bail!(InfiniteRecursionDetected)362 }363 file.evaluating = true;364 365 drop(file_cache);366 let res = evaluate(self.create_default_context(file_name), &parsed);367368 let mut file_cache = self.file_cache();369 let mut file = file_cache.entry(path.clone());370371 let Entry::Occupied(file) = &mut file else {372 unreachable!("this file was just here")373 };374 let file = file.get_mut();375 file.evaluating = false;376 match res {377 Ok(v) => {378 file.evaluated = Some(v.clone());379 Ok(v)380 }381 Err(e) => Err(e),382 }383 }384385 386 pub fn import_from(&self, from: &SourcePath, path: impl AsPathLike) -> Result<Val> {387 let resolved = self.resolve_from(from, &path)?;388 self.import_resolved(resolved)389 }390 pub fn import(&self, path: impl AsPathLike) -> Result<Val> {391 let resolved = self.resolve_from_default(&path)?;392 self.import_resolved(resolved)393 }394395 396 pub fn create_default_context(&self, source: Source) -> Context {397 self.context_initializer().initialize(source)398 }399400 401 pub fn create_default_context_with(402 &self,403 source: Source,404 context_initializer: impl ContextInitializer,405 ) -> Context {406 let default_initializer = self.context_initializer();407 let mut builder = ContextBuilder::with_capacity(408 default_initializer.reserve_vars() + context_initializer.reserve_vars(),409 );410 default_initializer.populate(source.clone(), &mut builder);411 context_initializer.populate(source, &mut builder);412413 builder.build()414 }415}416417418impl State {419 fn file_cache(&self) -> RefMut<'_, FxHashMap<SourcePath, FileData>> {420 self.0.file_cache.borrow_mut()421 }422}423424pub fn in_frame<T>(425 e: CallLocation<'_>,426 frame_desc: impl FnOnce() -> String,427 f: impl FnOnce() -> Result<T>,428) -> Result<T> {429 let _guard = check_depth()?;430431 f().with_description_src(e, frame_desc)432}433434435pub fn in_description_frame<T>(436 frame_desc: impl FnOnce() -> String,437 f: impl FnOnce() -> Result<T>,438) -> Result<T> {439 let _guard = check_depth()?;440441 f().with_description(frame_desc)442}443444#[derive(Trace)]445pub struct InitialUnderscore(pub Thunk<Val>);446impl ContextInitializer for InitialUnderscore {447 fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {448 builder.bind("_", self.0.clone());449 }450451 fn as_any(&self) -> &dyn Any {452 self453 }454}455456457impl State {458 459 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {460 let code = code.into();461 let source = Source::new_virtual(name.into(), code.clone());462 let parsed = jrsonnet_parser::parse(463 &code,464 &ParserSettings {465 source: source.clone(),466 },467 )468 .map_err(|e| ImportSyntaxError {469 path: source.clone(),470 error: Box::new(e),471 })?;472 evaluate(self.create_default_context(source), &parsed)473 }474 475 pub fn evaluate_snippet_with(476 &self,477 name: impl Into<IStr>,478 code: impl Into<IStr>,479 context_initializer: impl ContextInitializer,480 ) -> Result<Val> {481 let code = code.into();482 let source = Source::new_virtual(name.into(), code.clone());483 let parsed = jrsonnet_parser::parse(484 &code,485 &ParserSettings {486 source: source.clone(),487 },488 )489 .map_err(|e| ImportSyntaxError {490 path: source.clone(),491 error: Box::new(e),492 })?;493 evaluate(494 self.create_default_context_with(source, context_initializer),495 &parsed,496 )497 }498}499500501impl State {502 503 #[allow(clippy::missing_panics_doc)]504 pub fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result<SourcePath> {505 self.import_resolver().resolve_from(from, path)506 }507 #[allow(clippy::missing_panics_doc)]508 pub fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result<SourcePath> {509 self.import_resolver().resolve_from_default(path)510 }511 pub fn import_resolver(&self) -> &dyn ImportResolver {512 &*self.0.import_resolver513 }514 pub fn context_initializer(&self) -> &dyn ContextInitializer {515 &*self.0.context_initializer.0516 }517}518519impl State {520 pub fn builder() -> StateBuilder {521 StateBuilder::default()522 }523}524525impl Default for State {526 fn default() -> Self {527 Self::builder().build()528 }529}530531#[derive(Default)]532pub struct StateBuilder {533 import_resolver: Option<Rc<dyn ImportResolver>>,534 context_initializer: Option<CcContextInitializer>,535}536impl StateBuilder {537 pub fn import_resolver(&mut self, import_resolver: impl ImportResolver) -> &mut Self {538 let _ = self.import_resolver.insert(Rc::new(import_resolver));539 self540 }541 pub fn context_initializer(542 &mut self,543 context_initializer: impl ContextInitializer,544 ) -> &mut Self {545 let _ = self546 .context_initializer547 .insert(CcContextInitializer::new(context_initializer));548 self549 }550 pub fn build(mut self) -> State {551 State(Cc::new(EvaluationStateInternals {552 file_cache: RefCell::new(FxHashMap::new()),553 context_initializer: self554 .context_initializer555 .take()556 .unwrap_or_else(|| CcContextInitializer::new(())),557 import_resolver: self558 .import_resolver559 .take()560 .unwrap_or_else(|| Rc::new(DummyImportResolver)),561 }))562 }563}