1#![feature(box_syntax, box_patterns)]2#![feature(type_alias_impl_trait)]3#![feature(debug_non_exhaustive)]4#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]5#![feature(stmt_expr_attributes)]6mod ctx;7mod dynamic;8mod error;9mod evaluate;10mod function;11mod obj;12mod val;1314pub use ctx::*;15pub use dynamic::*;16pub use error::*;17pub use evaluate::*;18use jsonnet_parser::*;19pub use obj::*;20use std::{cell::RefCell, collections::HashMap, fmt::Debug, path::PathBuf, rc::Rc};21pub use val::*;2223rc_fn_helper!(24 Binding,25 binding,26 dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<Val>27);28rc_fn_helper!(FunctionRhs, function_rhs, dyn Fn(Context) -> Result<Val>);29rc_fn_helper!(30 FunctionDefault,31 function_default,32 dyn Fn(Context, LocExpr) -> Result<Val>33);3435#[derive(Clone)]36pub enum LazyBinding {37 Bindable(Rc<dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<LazyVal>>),38 Bound(LazyVal),39}4041impl Debug for LazyBinding {42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {43 write!(f, "LazyBinding")44 }45}46impl LazyBinding {47 pub fn evaluate(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<LazyVal> {48 match self {49 LazyBinding::Bindable(v) => v(this, super_obj),50 LazyBinding::Bound(v) => Ok(v.clone()),51 }52 }53}5455pub struct EvaluationSettings {56 max_stack_frames: usize,57 max_stack_trace_size: usize,58}59impl Default for EvaluationSettings {60 fn default() -> Self {61 EvaluationSettings {62 max_stack_frames: 500,63 max_stack_trace_size: 20,64 }65 }66}6768pub struct FileData(String, LocExpr, Option<Val>);69#[derive(Default)]70pub struct EvaluationStateInternals {71 72 stack: RefCell<Vec<StackTraceElement>>,73 74 75 files: RefCell<HashMap<PathBuf, FileData>>,76 globals: RefCell<HashMap<String, Val>>,7778 settings: EvaluationSettings,79}8081thread_local! {82 83 84 pub(crate) static EVAL_STATE: RefCell<Option<EvaluationState>> = RefCell::new(None)85}86#[inline(always)]87pub(crate) fn with_state<T>(f: impl FnOnce(&EvaluationState) -> T) -> T {88 EVAL_STATE.with(89 #[inline(always)]90 |s| f(s.borrow().as_ref().unwrap()),91 )92}93pub(crate) fn create_error<T>(err: Error) -> Result<T> {94 with_state(|s| s.error(err))95}96#[inline(always)]97pub(crate) fn push<T>(e: LocExpr, comment: String, f: impl FnOnce() -> Result<T>) -> Result<T> {98 with_state(|s| s.push(e, comment, f))99}100101102#[derive(Default, Clone)]103pub struct EvaluationState(Rc<EvaluationStateInternals>);104impl EvaluationState {105 pub fn add_file(&self, name: PathBuf, code: String) -> std::result::Result<(), ParseError> {106 self.0.files.borrow_mut().insert(107 name.clone(),108 FileData(109 code.clone(),110 parse(111 &code,112 &ParserSettings {113 file_name: name,114 loc_data: true,115 },116 )?,117 None,118 ),119 );120121 Ok(())122 }123 pub fn add_parsed_file(124 &self,125 name: PathBuf,126 code: String,127 parsed: LocExpr,128 ) -> std::result::Result<(), ()> {129 self.0130 .files131 .borrow_mut()132 .insert(name, FileData(code, parsed, None));133134 Ok(())135 }136 pub fn get_source(&self, name: &PathBuf) -> Option<String> {137 let ro_map = self.0.files.borrow();138 ro_map.get(name).map(|value| value.0.clone())139 }140 pub fn evaluate_file(&self, name: &PathBuf) -> Result<Val> {141 self.begin_state();142 let expr: LocExpr = {143 let ro_map = self.0.files.borrow();144 let value = ro_map145 .get(name)146 .unwrap_or_else(|| panic!("file not added: {:?}", name));147 if value.2.is_some() {148 return Ok(value.2.clone().unwrap());149 }150 value.1.clone()151 };152 let value = evaluate(self.create_default_context()?, &expr)?;153 {154 self.0155 .files156 .borrow_mut()157 .get_mut(name)158 .unwrap()159 .2160 .replace(value.clone());161 }162 self.end_state();163 Ok(value)164 }165166 pub fn parse_evaluate_raw(&self, code: &str) -> Result<Val> {167 let parsed = parse(168 &code,169 &ParserSettings {170 file_name: PathBuf::from("raw.jsonnet"),171 loc_data: true,172 },173 );174 self.begin_state();175 let value = evaluate(self.create_default_context()?, &parsed.unwrap());176 self.end_state();177 value178 }179180 pub fn add_global(&self, name: String, value: Val) {181 self.0.globals.borrow_mut().insert(name, value);182 }183184 pub fn add_stdlib(&self) {185 self.begin_state();186 use jsonnet_stdlib::STDLIB_STR;187 if cfg!(feature = "serialized-stdlib") {188 self.add_parsed_file(189 PathBuf::from("std.jsonnet"),190 STDLIB_STR.to_owned(),191 bincode::deserialize(include_bytes!(concat!(env!("OUT_DIR"), "/stdlib.bincode")))192 .expect("deserialize stdlib"),193 )194 .unwrap();195 } else {196 self.add_file(PathBuf::from("std.jsonnet"), STDLIB_STR.to_owned())197 .unwrap();198 }199 let val = self.evaluate_file(&PathBuf::from("std.jsonnet")).unwrap();200 self.add_global("std".to_owned(), val);201 self.end_state();202 }203204 pub fn create_default_context(&self) -> Result<Context> {205 let globals = self.0.globals.borrow();206 let mut new_bindings: HashMap<String, LazyBinding> = HashMap::new();207 for (name, value) in globals.iter() {208 new_bindings.insert(209 name.clone(),210 LazyBinding::Bound(resolved_lazy_val!(value.clone())),211 );212 }213 Context::new().extend(new_bindings, None, None, None)214 }215216 #[inline(always)]217 pub fn push<T>(&self, e: LocExpr, comment: String, f: impl FnOnce() -> Result<T>) -> Result<T> {218 {219 let mut stack = self.0.stack.borrow_mut();220 if stack.len() > self.0.settings.max_stack_frames {221 drop(stack);222 return self.error(Error::StackOverflow);223 } else {224 stack.push(StackTraceElement(e, comment));225 }226 }227 let result = f();228 self.0.stack.borrow_mut().pop();229 result230 }231 pub fn print_stack_trace(&self) {232 for e in self.stack_trace().0 {233 println!("{:?} - {:?}", e.0, e.1)234 }235 }236 pub fn stack_trace(&self) -> StackTrace {237 StackTrace(238 self.0239 .stack240 .borrow()241 .iter()242 .rev()243 .take(self.0.settings.max_stack_trace_size)244 .cloned()245 .collect(),246 )247 }248 pub fn error<T>(&self, err: Error) -> Result<T> {249 Err(LocError(err, self.stack_trace()))250 }251252 fn begin_state(&self) {253 EVAL_STATE.with(|v| v.borrow_mut().replace(self.clone()));254 }255 fn end_state(&self) {256 EVAL_STATE.with(|v| v.borrow_mut().take());257 }258}259260#[cfg(test)]261pub mod tests {262 use super::Val;263 use crate::EvaluationState;264 use jsonnet_parser::*;265 use std::path::PathBuf;266267 #[test]268 fn eval_state_stacktrace() {269 let state = EvaluationState::default();270 state271 .push(272 loc_expr!(273 Expr::Num(0.0),274 true,275 (PathBuf::from("test1.jsonnet"), 10, 20)276 ),277 "outer".to_owned(),278 || {279 state.push(280 loc_expr!(281 Expr::Num(0.0),282 true,283 (PathBuf::from("test2.jsonnet"), 30, 40)284 ),285 "inner".to_owned(),286 || {287 state.print_stack_trace();288 Ok(())289 },290 )?;291 Ok(())292 },293 )294 .unwrap();295 }296297 #[test]298 fn eval_state_standard() {299 let state = EvaluationState::default();300 state.add_stdlib();301 assert_eq!(302 state303 .parse_evaluate_raw(r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#)304 .unwrap(),305 Val::Bool(true)306 );307 }308309 macro_rules! eval {310 ($str: expr) => {311 evaluate(312 Context::new(),313 EvaluationState::default(),314 &parse(315 $str,316 &ParserSettings {317 loc_data: true,318 file_name: "test.jsonnet".to_owned(),319 },320 )321 .unwrap(),322 )323 };324 }325326 macro_rules! eval_stdlib {327 ($str: expr) => {{328 let std = "local std = ".to_owned() + jsonnet_stdlib::STDLIB_STR + ";";329 evaluate(330 Context::new(),331 EvaluationState::default(),332 &parse(333 &(std + $str),334 &ParserSettings {335 loc_data: true,336 file_name: "test.jsonnet".to_owned(),337 },338 )339 .unwrap(),340 )341 }};342 }343344 macro_rules! assert_eval {345 ($str: expr) => {346 assert_eq!(347 evaluate(348 Context::new(),349 EvaluationState::default(),350 &parse(351 $str,352 &ParserSettings {353 loc_data: true,354 file_name: "test.jsonnet".to_owned(),355 }356 )357 .unwrap()358 ),359 Val::Bool(true)360 )361 };362 }363 macro_rules! assert_json {364 ($str: expr, $out: expr) => {365 assert_eq!(366 format!(367 "{}",368 evaluate(369 Context::new(),370 EvaluationState::default(),371 &parse(372 $str,373 &ParserSettings {374 loc_data: true,375 file_name: "test.jsonnet".to_owned(),376 }377 )378 .unwrap()379 )380 ),381 $out382 )383 };384 }385 macro_rules! assert_json_stdlib {386 ($str: expr, $out: expr) => {387 assert_eq!(format!("{}", eval_stdlib!($str)), $out)388 };389 }390 macro_rules! assert_eval_neg {391 ($str: expr) => {392 assert_eq!(393 evaluate(394 Context::new(),395 EvaluationState::default(),396 &parse(397 $str,398 &ParserSettings {399 loc_data: true,400 file_name: "test.jsonnet".to_owned(),401 }402 )403 .unwrap()404 ),405 Val::Bool(false)406 )407 };408 }409410 411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621}