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 collections::hash_map::Entry,31 fmt::{self, Debug},32 rc::Rc,33};3435pub use ctx::*;36pub use dynamic::*;37pub use error::{Error, ErrorKind::*, Result, ResultExt};38pub use evaluate::*;39use function::CallLocation;40pub use import::*;41use jrsonnet_gcmodule::{cc_dyn, Cc, Trace};42pub use jrsonnet_interner::{IBytes, IStr};43#[doc(hidden)]44pub use jrsonnet_macros;45pub use jrsonnet_parser as parser;46use jrsonnet_parser::{LocExpr, ParserSettings, Source, SourcePath};47pub use obj::*;48pub use rustc_hash;49use rustc_hash::FxHashMap;50use stack::check_depth;51pub use tla::apply_tla;52pub use val::{Thunk, Val};5354use crate::gc::WithCapacityExt as _;5556cc_dyn!(57 #[derive(Clone)]58 CcUnbound<V>,59 Unbound<Bound = V>60);61626364pub trait Unbound: Trace {65 66 type Bound;67 68 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;69}70717273#[derive(Clone, Trace)]74pub enum MaybeUnbound {75 76 Unbound(CcUnbound<Val>),77 78 Bound(Thunk<Val>),79}8081impl Debug for MaybeUnbound {82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {83 write!(f, "MaybeUnbound")84 }85}86impl MaybeUnbound {87 88 pub fn evaluate(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {89 match self {90 Self::Unbound(v) => v.0.bind(sup, this),91 Self::Bound(v) => Ok(v.evaluate()?),92 }93 }94}9596cc_dyn!(CcContextInitializer, ContextInitializer);979899100pub trait ContextInitializer: Trace {101 102 fn reserve_vars(&self) -> usize {103 0104 }105 106 107 108 fn initialize(&self, state: State, for_file: Source) -> Context {109 let mut builder = ContextBuilder::with_capacity(state, self.reserve_vars());110 self.populate(for_file, &mut builder);111 builder.build()112 }113 114 115 fn populate(&self, for_file: Source, builder: &mut ContextBuilder);116 117 118 fn as_any(&self) -> &dyn Any;119}120121122impl ContextInitializer for () {123 fn populate(&self, _for_file: Source, _builder: &mut ContextBuilder) {}124 fn as_any(&self) -> &dyn Any {125 self126 }127}128129impl<T> ContextInitializer for Option<T>130where131 T: ContextInitializer,132{133 fn initialize(&self, state: State, for_file: Source) -> Context {134 if let Some(ctx) = self {135 ctx.initialize(state, for_file)136 } else {137 ().initialize(state, for_file)138 }139 }140141 fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {142 if let Some(ctx) = self {143 ctx.populate(for_file, builder);144 }145 }146147 fn as_any(&self) -> &dyn Any {148 self149 }150}151152macro_rules! impl_context_initializer {153 ($($gen:ident)*) => {154 #[allow(non_snake_case)]155 impl<$($gen: ContextInitializer + Trace,)*> ContextInitializer for ($($gen,)*) {156 fn reserve_vars(&self) -> usize {157 let mut out = 0;158 let ($($gen,)*) = self;159 $(out += $gen.reserve_vars();)*160 out161 }162 fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {163 let ($($gen,)*) = self;164 $($gen.populate(for_file.clone(), builder);)*165 }166 fn as_any(&self) -> &dyn Any {167 self168 }169 }170 };171 ($($cur:ident)* @ $c:ident $($rest:ident)*) => {172 impl_context_initializer!($($cur)*);173 impl_context_initializer!($($cur)* $c @ $($rest)*);174 };175 ($($cur:ident)* @) => {176 impl_context_initializer!($($cur)*);177 }178}179impl_context_initializer! {180 A @ B C D E F G181}182183#[derive(Trace)]184struct FileData {185 string: Option<IStr>,186 bytes: Option<IBytes>,187 parsed: Option<LocExpr>,188 evaluated: Option<Val>,189190 evaluating: bool,191}192impl FileData {193 fn new_string(data: IStr) -> Self {194 Self {195 string: Some(data),196 bytes: None,197 parsed: None,198 evaluated: None,199 evaluating: false,200 }201 }202 fn new_bytes(data: IBytes) -> Self {203 Self {204 string: None,205 bytes: Some(data),206 parsed: None,207 evaluated: None,208 evaluating: false,209 }210 }211 pub(crate) fn get_string(&mut self) -> Option<IStr> {212 if self.string.is_none() {213 self.string = Some(214 self.bytes215 .as_ref()216 .expect("either string or bytes should be set")217 .clone()218 .cast_str()?,219 );220 }221 Some(self.string.clone().expect("just set"))222 }223}224225#[derive(Trace)]226pub struct EvaluationStateInternals {227 228 file_cache: RefCell<FxHashMap<SourcePath, FileData>>,229 230 231 context_initializer: CcContextInitializer,232 233 import_resolver: Rc<dyn ImportResolver>,234}235236237#[derive(Clone, Trace)]238pub struct State(Cc<EvaluationStateInternals>);239240impl State {241 242 pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {243 let mut file_cache = self.file_cache();244 let mut file = file_cache.entry(path.clone());245246 let file = match file {247 Entry::Occupied(ref mut d) => d.get_mut(),248 Entry::Vacant(v) => {249 let data = self.import_resolver().load_file_contents(&path)?;250 v.insert(FileData::new_string(251 std::str::from_utf8(&data)252 .map_err(|_| ImportBadFileUtf8(path.clone()))?253 .into(),254 ))255 }256 };257 Ok(file258 .get_string()259 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?)260 }261 262 pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {263 let mut file_cache = self.file_cache();264 let mut file = file_cache.entry(path.clone());265266 let file = match file {267 Entry::Occupied(ref mut d) => d.get_mut(),268 Entry::Vacant(v) => {269 let data = self.import_resolver().load_file_contents(&path)?;270 v.insert(FileData::new_bytes(data.as_slice().into()))271 }272 };273 if let Some(str) = &file.bytes {274 return Ok(str.clone());275 }276 if file.bytes.is_none() {277 file.bytes = Some(278 file.string279 .as_ref()280 .expect("either string or bytes should be set")281 .clone()282 .cast_bytes(),283 );284 }285 Ok(file.bytes.as_ref().expect("just set").clone())286 }287 288 pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {289 let mut file_cache = self.file_cache();290 let mut file = file_cache.entry(path.clone());291292 let file = match file {293 Entry::Occupied(ref mut d) => d.get_mut(),294 Entry::Vacant(v) => {295 let data = self.import_resolver().load_file_contents(&path)?;296 v.insert(FileData::new_string(297 std::str::from_utf8(&data)298 .map_err(|_| ImportBadFileUtf8(path.clone()))?299 .into(),300 ))301 }302 };303 if let Some(val) = &file.evaluated {304 return Ok(val.clone());305 }306 let code = file307 .get_string()308 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?;309 let file_name = Source::new(path.clone(), code.clone());310 if file.parsed.is_none() {311 file.parsed = Some(312 jrsonnet_parser::parse(313 &code,314 &ParserSettings {315 source: file_name.clone(),316 },317 )318 .map_err(|e| ImportSyntaxError {319 path: file_name.clone(),320 error: Box::new(e),321 })?,322 );323 }324 let parsed = file.parsed.as_ref().expect("just set").clone();325 if file.evaluating {326 bail!(InfiniteRecursionDetected)327 }328 file.evaluating = true;329 330 drop(file_cache);331 let res = evaluate(self.create_default_context(file_name), &parsed);332333 let mut file_cache = self.file_cache();334 let mut file = file_cache.entry(path.clone());335336 let Entry::Occupied(file) = &mut file else {337 unreachable!("this file was just here")338 };339 let file = file.get_mut();340 file.evaluating = false;341 match res {342 Ok(v) => {343 file.evaluated = Some(v.clone());344 Ok(v)345 }346 Err(e) => Err(e),347 }348 }349350 351 pub fn import_from(&self, from: &SourcePath, path: impl AsPathLike) -> Result<Val> {352 let resolved = self.resolve_from(from, &path)?;353 self.import_resolved(resolved)354 }355 pub fn import(&self, path: impl AsPathLike) -> Result<Val> {356 let resolved = self.resolve_from_default(&path)?;357 self.import_resolved(resolved)358 }359360 361 pub fn create_default_context(&self, source: Source) -> Context {362 self.context_initializer().initialize(self.clone(), source)363 }364365 366 pub fn create_default_context_with(367 &self,368 source: Source,369 context_initializer: impl ContextInitializer,370 ) -> Context {371 let default_initializer = self.context_initializer();372 let mut builder = ContextBuilder::with_capacity(373 self.clone(),374 default_initializer.reserve_vars() + context_initializer.reserve_vars(),375 );376 default_initializer.populate(source.clone(), &mut builder);377 context_initializer.populate(source, &mut builder);378379 builder.build()380 }381}382383384impl State {385 fn file_cache(&self) -> RefMut<'_, FxHashMap<SourcePath, FileData>> {386 self.0.file_cache.borrow_mut()387 }388}389390pub fn in_frame<T>(391 e: CallLocation<'_>,392 frame_desc: impl FnOnce() -> String,393 f: impl FnOnce() -> Result<T>,394) -> Result<T> {395 let _guard = check_depth()?;396397 f().with_description_src(e, frame_desc)398}399400401pub fn in_description_frame<T>(402 frame_desc: impl FnOnce() -> String,403 f: impl FnOnce() -> Result<T>,404) -> Result<T> {405 let _guard = check_depth()?;406407 f().with_description(frame_desc)408}409410#[derive(Trace)]411pub struct InitialUnderscore(pub Thunk<Val>);412impl ContextInitializer for InitialUnderscore {413 fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {414 builder.bind("_", self.0.clone());415 }416417 fn as_any(&self) -> &dyn Any {418 self419 }420}421422423impl State {424 425 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {426 let code = code.into();427 let source = Source::new_virtual(name.into(), code.clone());428 let parsed = jrsonnet_parser::parse(429 &code,430 &ParserSettings {431 source: source.clone(),432 },433 )434 .map_err(|e| ImportSyntaxError {435 path: source.clone(),436 error: Box::new(e),437 })?;438 evaluate(self.create_default_context(source), &parsed)439 }440 441 pub fn evaluate_snippet_with(442 &self,443 name: impl Into<IStr>,444 code: impl Into<IStr>,445 context_initializer: impl ContextInitializer,446 ) -> Result<Val> {447 let code = code.into();448 let source = Source::new_virtual(name.into(), code.clone());449 let parsed = jrsonnet_parser::parse(450 &code,451 &ParserSettings {452 source: source.clone(),453 },454 )455 .map_err(|e| ImportSyntaxError {456 path: source.clone(),457 error: Box::new(e),458 })?;459 evaluate(460 self.create_default_context_with(source, context_initializer),461 &parsed,462 )463 }464}465466467impl State {468 469 #[allow(clippy::missing_panics_doc)]470 pub fn resolve_from(&self, from: &SourcePath, path: &dyn AsPathLike) -> Result<SourcePath> {471 self.import_resolver().resolve_from(from, path)472 }473 #[allow(clippy::missing_panics_doc)]474 pub fn resolve_from_default(&self, path: &dyn AsPathLike) -> Result<SourcePath> {475 self.import_resolver().resolve_from_default(path)476 }477 pub fn import_resolver(&self) -> &dyn ImportResolver {478 &*self.0.import_resolver479 }480 pub fn context_initializer(&self) -> &dyn ContextInitializer {481 &*self.0.context_initializer.0482 }483}484485impl State {486 pub fn builder() -> StateBuilder {487 StateBuilder::default()488 }489}490491impl Default for State {492 fn default() -> Self {493 Self::builder().build()494 }495}496497#[derive(Default)]498pub struct StateBuilder {499 import_resolver: Option<Rc<dyn ImportResolver>>,500 context_initializer: Option<CcContextInitializer>,501}502impl StateBuilder {503 pub fn import_resolver(&mut self, import_resolver: impl ImportResolver) -> &mut Self {504 let _ = self.import_resolver.insert(Rc::new(import_resolver));505 self506 }507 pub fn context_initializer(508 &mut self,509 context_initializer: impl ContextInitializer,510 ) -> &mut Self {511 let _ = self512 .context_initializer513 .insert(CcContextInitializer::new(context_initializer));514 self515 }516 pub fn build(mut self) -> State {517 State(Cc::new(EvaluationStateInternals {518 file_cache: RefCell::new(FxHashMap::new()),519 context_initializer: self520 .context_initializer521 .take()522 .unwrap_or_else(|| CcContextInitializer::new(())),523 import_resolver: self524 .import_resolver525 .take()526 .unwrap_or_else(|| Rc::new(DummyImportResolver)),527 }))528 }529}