difftreelog
feat(evaluator) tailstrict calls
in: master
4 files changed
crates/jsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/error.rs
+++ b/crates/jsonnet-evaluator/src/error.rs
@@ -9,6 +9,8 @@
RuntimeError(String),
StackOverflow,
+ FractionalIndex,
+ DivisionByZero,
}
#[derive(Clone, Debug)]
crates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/evaluate.rs
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -135,7 +135,12 @@
// Num X Num
(Val::Num(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Num(v1 * v2),
- (Val::Num(v1), BinaryOpType::Div, Val::Num(v2)) => Val::Num(v1 / v2),
+ (Val::Num(v1), BinaryOpType::Div, Val::Num(v2)) => {
+ if *v2 <= f64::EPSILON {
+ create_error(crate::Error::DivisionByZero)?
+ }
+ Val::Num(v1 / v2)
+ }
(Val::Num(v1), BinaryOpType::Sub, Val::Num(v2)) => Val::Num(v1 - v2),
@@ -305,6 +310,7 @@
})
}
+#[inline(always)]
pub fn evaluate(context: Context, expr: &LocExpr) -> Result<Val> {
use Expr::*;
let locexpr = expr.clone();
@@ -356,11 +362,15 @@
create_error(crate::Error::NoSuchField(s))?
}
}
- (Val::Arr(v), Val::Num(n)) => v
- .get(n as usize)
- .unwrap_or_else(|| panic!("out of bounds"))
- .clone()
- .unwrap_if_lazy()?,
+ (Val::Arr(v), Val::Num(n)) => {
+ if n.fract() > f64::EPSILON {
+ create_error(crate::Error::FractionalIndex)?
+ }
+ v.get(n as usize)
+ .unwrap_or_else(|| panic!("out of bounds"))
+ .clone()
+ .unwrap_if_lazy()?
+ }
(Val::Str(s), Val::Num(n)) => {
Val::Str(s.chars().skip(n as usize).take(1).collect())
}
@@ -511,29 +521,52 @@
panic!("bad trace call");
}
}
+ ("std", "pow") => {
+ assert_eq!(args.len(), 2);
+ if let (Val::Num(a), Val::Num(b)) = (
+ evaluate(context.clone(), &args[0].1)?,
+ evaluate(context, &args[1].1)?,
+ ) {
+ Val::Num(a.powf(b))
+ } else {
+ panic!("bad pow call");
+ }
+ }
(ns, name) => panic!("Intristic not found: {}.{}", ns, name),
},
- Val::Func(f) => push(locexpr, "function call".to_owned(), || {
- f.evaluate(
- args.clone()
- .into_iter()
- .map(move |a| {
- (
- a.clone().0,
- if *tailstrict {
- Val::Lazy(LazyVal::new_resolved(
- evaluate(context.clone(), &a.1).unwrap(),
+ Val::Func(f) => {
+ let body = #[inline(always)]
+ || {
+ f.evaluate(
+ args.clone()
+ .into_iter()
+ .map(
+ #[inline(always)]
+ move |a| {
+ Ok((
+ a.clone().0,
+ if *tailstrict {
+ Val::Lazy(LazyVal::new_resolved(evaluate(
+ context.clone(),
+ &a.1,
+ )?))
+ } else {
+ Val::Lazy(lazy_val!(
+ closure!(clone context, clone a, || evaluate(context.clone(), &a.clone().1))
+ ))
+ },
))
- } else {
- Val::Lazy(lazy_val!(
- closure!(clone context, clone a, || evaluate(context.clone(), &a.clone().1))
- ))
},
)
- })
- .collect(),
- )
- })?,
+ .collect::<Result<Vec<_>>>()?,
+ )
+ };
+ if *tailstrict {
+ body()?
+ } else {
+ push(locexpr, "function call".to_owned(), body)?
+ }
+ }
_ => panic!("{:?} is not a function", value),
}
}
@@ -578,9 +611,7 @@
)?
} else {
match cond_else {
- Some(v) => push(v.clone(), "if condition 'else' branch".to_owned(), || {
- evaluate(context, v)
- })?,
+ Some(v) => evaluate(context, v)?,
None => Val::Null,
}
}
crates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth1#![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)]5mod ctx;6mod dynamic;7mod error;8mod evaluate;9mod function;10mod obj;11mod val;1213pub use ctx::*;14pub use dynamic::*;15pub use error::*;16pub use evaluate::*;17use jsonnet_parser::*;18pub use obj::*;19use std::{cell::RefCell, collections::HashMap, fmt::Debug, path::PathBuf, rc::Rc};20pub use val::*;2122rc_fn_helper!(23 Binding,24 binding,25 dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<Val>26);27rc_fn_helper!(FunctionRhs, function_rhs, dyn Fn(Context) -> Result<Val>);28rc_fn_helper!(29 FunctionDefault,30 function_default,31 dyn Fn(Context, LocExpr) -> Result<Val>32);3334#[derive(Clone)]35pub enum LazyBinding {36 Bindable(Rc<dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<LazyVal>>),37 Bound(LazyVal),38}3940impl Debug for LazyBinding {41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {42 write!(f, "LazyBinding")43 }44}45impl LazyBinding {46 pub fn evaluate(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<LazyVal> {47 match self {48 LazyBinding::Bindable(v) => v(this, super_obj),49 LazyBinding::Bound(v) => Ok(v.clone()),50 }51 }52}5354pub struct FileData(String, LocExpr, Option<Val>);55#[derive(Default)]56pub struct EvaluationStateInternals {57 /// Used for stack-overflows and stacktraces58 stack: RefCell<Vec<StackTraceElement>>,59 /// Contains file source codes and evaluated results for imports and pretty60 /// printing stacktraces61 files: RefCell<HashMap<PathBuf, FileData>>,62 globals: RefCell<HashMap<String, Val>>,63}6465thread_local! {66 pub static EVAL_STATE: RefCell<Option<EvaluationState>> = RefCell::new(None)67}68pub(crate) fn with_state<T>(f: impl FnOnce(&EvaluationState) -> T) -> T {69 EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap()))70}71pub(crate) fn create_error<T>(err: Error) -> Result<T> {72 with_state(|s| s.error(err))73}74pub(crate) fn push<T>(e: LocExpr, comment: String, f: impl FnOnce() -> Result<T>) -> Result<T> {75 with_state(|s| s.push(e, comment, f))76}7778#[derive(Default, Clone)]79pub struct EvaluationState(Rc<EvaluationStateInternals>);80impl EvaluationState {81 pub fn add_file(&self, name: PathBuf, code: String) -> std::result::Result<(), ParseError> {82 self.0.files.borrow_mut().insert(83 name.clone(),84 FileData(85 code.clone(),86 parse(87 &code,88 &ParserSettings {89 file_name: name,90 loc_data: true,91 },92 )?,93 None,94 ),95 );9697 Ok(())98 }99 pub fn add_parsed_file(100 &self,101 name: PathBuf,102 code: String,103 parsed: LocExpr,104 ) -> std::result::Result<(), ()> {105 self.0106 .files107 .borrow_mut()108 .insert(name, FileData(code, parsed, None));109110 Ok(())111 }112 pub fn get_source(&self, name: &PathBuf) -> Option<String> {113 let ro_map = self.0.files.borrow();114 ro_map.get(name).map(|value| value.0.clone())115 }116 pub fn evaluate_file(&self, name: &PathBuf) -> Result<Val> {117 self.begin_state();118 let expr: LocExpr = {119 let ro_map = self.0.files.borrow();120 let value = ro_map121 .get(name)122 .unwrap_or_else(|| panic!("file not added: {:?}", name));123 if value.2.is_some() {124 return Ok(value.2.clone().unwrap());125 }126 value.1.clone()127 };128 let value = evaluate(self.create_default_context()?, &expr)?;129 {130 self.0131 .files132 .borrow_mut()133 .get_mut(name)134 .unwrap()135 .2136 .replace(value.clone());137 }138 self.end_state();139 Ok(value)140 }141142 pub fn parse_evaluate_raw(&self, code: &str) -> Result<Val> {143 let parsed = parse(144 &code,145 &ParserSettings {146 file_name: PathBuf::from("raw.jsonnet"),147 loc_data: true,148 },149 );150 self.begin_state();151 let value = evaluate(self.create_default_context()?, &parsed.unwrap());152 self.end_state();153 value154 }155156 pub fn add_global(&self, name: String, value: Val) {157 self.0.globals.borrow_mut().insert(name, value);158 }159160 pub fn add_stdlib(&self) {161 self.begin_state();162 use jsonnet_stdlib::STDLIB_STR;163 if cfg!(feature = "serialized-stdlib") {164 self.add_parsed_file(165 PathBuf::from("std.jsonnet"),166 STDLIB_STR.to_owned(),167 bincode::deserialize(include_bytes!(concat!(env!("OUT_DIR"), "/stdlib.bincode")))168 .expect("deserialize stdlib"),169 )170 .unwrap();171 } else {172 self.add_file(PathBuf::from("std.jsonnet"), STDLIB_STR.to_owned())173 .unwrap();174 }175 let val = self.evaluate_file(&PathBuf::from("std.jsonnet")).unwrap();176 self.add_global("std".to_owned(), val);177 self.end_state();178 }179180 pub fn create_default_context(&self) -> Result<Context> {181 let globals = self.0.globals.borrow();182 let mut new_bindings: HashMap<String, LazyBinding> = HashMap::new();183 for (name, value) in globals.iter() {184 new_bindings.insert(185 name.clone(),186 LazyBinding::Bound(resolved_lazy_val!(value.clone())),187 );188 }189 Context::new().extend(new_bindings, None, None, None)190 }191192 pub fn push<T>(&self, e: LocExpr, comment: String, f: impl FnOnce() -> Result<T>) -> Result<T> {193 {194 let mut stack = self.0.stack.borrow_mut();195 if stack.len() > 500 {196 drop(stack);197 return self.error(Error::StackOverflow);198 } else {199 stack.push(StackTraceElement(e, comment));200 }201 }202 let result = f();203 self.0.stack.borrow_mut().pop();204 result205 }206 pub fn print_stack_trace(&self) {207 for e in self.stack_trace().0 {208 println!("{:?} - {:?}", e.0, e.1)209 }210 }211 pub fn stack_trace(&self) -> StackTrace {212 StackTrace(self.0.stack.borrow().iter().rev().cloned().collect())213 }214 pub fn error<T>(&self, err: Error) -> Result<T> {215 Err(LocError(err, self.stack_trace()))216 }217218 fn begin_state(&self) {219 EVAL_STATE.with(|v| v.borrow_mut().replace(self.clone()));220 }221 fn end_state(&self) {222 EVAL_STATE.with(|v| v.borrow_mut().take());223 }224}225226#[cfg(test)]227pub mod tests {228 use super::Val;229 use crate::EvaluationState;230 use jsonnet_parser::*;231 use std::path::PathBuf;232233 #[test]234 fn eval_state_stacktrace() {235 let state = EvaluationState::default();236 state237 .push(238 loc_expr!(239 Expr::Num(0.0),240 true,241 (PathBuf::from("test1.jsonnet"), 10, 20)242 ),243 "outer".to_owned(),244 || {245 state.push(246 loc_expr!(247 Expr::Num(0.0),248 true,249 (PathBuf::from("test2.jsonnet"), 30, 40)250 ),251 "inner".to_owned(),252 || {253 state.print_stack_trace();254 Ok(())255 },256 )?;257 Ok(())258 },259 )260 .unwrap();261 }262263 #[test]264 fn eval_state_standard() {265 let state = EvaluationState::default();266 state.add_stdlib();267 assert_eq!(268 state269 .parse_evaluate_raw(r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#)270 .unwrap(),271 Val::Bool(true)272 );273 }274275 macro_rules! eval {276 ($str: expr) => {277 evaluate(278 Context::new(),279 EvaluationState::default(),280 &parse(281 $str,282 &ParserSettings {283 loc_data: true,284 file_name: "test.jsonnet".to_owned(),285 },286 )287 .unwrap(),288 )289 };290 }291292 macro_rules! eval_stdlib {293 ($str: expr) => {{294 let std = "local std = ".to_owned() + jsonnet_stdlib::STDLIB_STR + ";";295 evaluate(296 Context::new(),297 EvaluationState::default(),298 &parse(299 &(std + $str),300 &ParserSettings {301 loc_data: true,302 file_name: "test.jsonnet".to_owned(),303 },304 )305 .unwrap(),306 )307 }};308 }309310 macro_rules! assert_eval {311 ($str: expr) => {312 assert_eq!(313 evaluate(314 Context::new(),315 EvaluationState::default(),316 &parse(317 $str,318 &ParserSettings {319 loc_data: true,320 file_name: "test.jsonnet".to_owned(),321 }322 )323 .unwrap()324 ),325 Val::Bool(true)326 )327 };328 }329 macro_rules! assert_json {330 ($str: expr, $out: expr) => {331 assert_eq!(332 format!(333 "{}",334 evaluate(335 Context::new(),336 EvaluationState::default(),337 &parse(338 $str,339 &ParserSettings {340 loc_data: true,341 file_name: "test.jsonnet".to_owned(),342 }343 )344 .unwrap()345 )346 ),347 $out348 )349 };350 }351 macro_rules! assert_json_stdlib {352 ($str: expr, $out: expr) => {353 assert_eq!(format!("{}", eval_stdlib!($str)), $out)354 };355 }356 macro_rules! assert_eval_neg {357 ($str: expr) => {358 assert_eq!(359 evaluate(360 Context::new(),361 EvaluationState::default(),362 &parse(363 $str,364 &ParserSettings {365 loc_data: true,366 file_name: "test.jsonnet".to_owned(),367 }368 )369 .unwrap()370 ),371 Val::Bool(false)372 )373 };374 }375376 /*377 /// Sanity checking, before trusting to another tests378 #[test]379 fn equality_operator() {380 assert_eval!("2 == 2");381 assert_eval_neg!("2 != 2");382 assert_eval!("2 != 3");383 assert_eval_neg!("2 == 3");384 assert_eval!("'Hello' == 'Hello'");385 assert_eval_neg!("'Hello' != 'Hello'");386 assert_eval!("'Hello' != 'World'");387 assert_eval_neg!("'Hello' == 'World'");388 }389390 #[test]391 fn math_evaluation() {392 assert_eval!("2 + 2 * 2 == 6");393 assert_eval!("3 + (2 + 2 * 2) == 9");394 }395396 #[test]397 fn string_concat() {398 assert_eval!("'Hello' + 'World' == 'HelloWorld'");399 assert_eval!("'Hello' * 3 == 'HelloHelloHello'");400 assert_eval!("'Hello' + 'World' * 3 == 'HelloWorldWorldWorld'");401 }402403 #[test]404 fn local() {405 assert_eval!("local a = 2; local b = 3; a + b == 5");406 assert_eval!("local a = 1, b = a + 1; a + b == 3");407 assert_eval!("local a = 1; local a = 2; a == 2");408 }409410 #[test]411 fn object_lazyness() {412 assert_json!("local a = {a:error 'test'}; {}", r#"{}"#);413 }414415 #[test]416 fn object_inheritance() {417 assert_json!("{a: self.b} + {b:3}", r#"{"a":3,"b":3}"#);418 }419420 #[test]421 fn test_object() {422 assert_json!("{a:2}", r#"{"a":2}"#);423 assert_json!("{a:2+2}", r#"{"a":4}"#);424 assert_json!("{a:2}+{b:2}", r#"{"a":2,"b":2}"#);425 assert_json!("{b:3}+{b:2}", r#"{"b":2}"#);426 assert_json!("{b:3}+{b+:2}", r#"{"b":5}"#);427 assert_json!("local test='a'; {[test]:2}", r#"{"a":2}"#);428 assert_json!(429 r#"430 {431 name: "Alice",432 welcome: "Hello " + self.name + "!",433 }434 "#,435 r#"{"name":"Alice","welcome":"Hello Alice!"}"#436 );437 assert_json!(438 r#"439 {440 name: "Alice",441 welcome: "Hello " + self.name + "!",442 } + {443 name: "Bob"444 }445 "#,446 r#"{"name":"Bob","welcome":"Hello Bob!"}"#447 );448 }449450 #[test]451 fn functions() {452 assert_json!(r#"local a = function(b, c = 2) b + c; a(2)"#, "4");453 assert_json!(454 r#"local a = function(b, c = "Dear") b + c + d, d = "World"; a("Hello")"#,455 r#""HelloDearWorld""#456 );457 }458459 #[test]460 fn local_methods() {461 assert_json!(r#"local a(b, c = 2) = b + c; a(2)"#, "4");462 assert_json!(463 r#"local a(b, c = "Dear") = b + c + d, d = "World"; a("Hello")"#,464 r#""HelloDearWorld""#465 );466 }467468 #[test]469 fn object_locals() {470 assert_json!(r#"{local a = 3, b: a}"#, r#"{"b":3}"#);471 assert_json!(r#"{local a = 3, local c = a, b: c}"#, r#"{"b":3}"#);472 assert_json!(473 r#"{local a = function (b) {[b]:4}, test: a("test")}"#,474 r#"{"test":{"test":4}}"#475 );476 }477478 #[test]479 fn direct_self() {480 println!(481 "{:#?}",482 eval!(483 r#"484 {485 local me = self,486 a: 3,487 b(): me.a,488 }489 "#490 )491 );492 }493494 #[test]495 fn indirect_self() {496 // `self` assigned to `me` was lost when being497 // referenced from field498 eval_stdlib!(499 r#"{500 local me = self,501 a: 3,502 b: me.a,503 }.b"#504 );505 }506507 // We can't trust other tests (And official jsonnet testsuite), if assert is not working correctly508 #[test]509 fn std_assert_ok() {510 eval_stdlib!("std.assertEqual(4.5 << 2, 16)");511 }512513 #[test]514 #[should_panic]515 fn std_assert_failure() {516 eval_stdlib!("std.assertEqual(4.5 << 2, 15)");517 }518519 #[test]520 fn string_is_string() {521 assert_eq!(522 eval_stdlib!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"),523 Val::Bool(false)524 );525 }526527 #[test]528 fn base64_works() {529 assert_json_stdlib!(r#"std.base64("test")"#, r#""dGVzdA==""#);530 }531532 #[test]533 fn utf8_chars() {534 assert_json_stdlib!(535 r#"local c="😎";{c:std.codepoint(c),l:std.length(c)}"#,536 r#"{"c":128526,"l":1}"#537 )538 }539540 #[test]541 fn json() {542 assert_json_stdlib!(543 r#"std.manifestJsonEx({a:3, b:4, c:6},"")"#,544 r#""{\n"a": 3,\n"b": 4,\n"c": 6\n}""#545 );546 }547548 #[test]549 fn test() {550 assert_json_stdlib!(551 r#"[[a, b] for a in [1,2,3] for b in [4,5,6]]"#,552 "[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]"553 );554 }555556 #[test]557 fn sjsonnet() {558 eval!(559 r#"560 local x0 = {k: 1};561 local x1 = {k: x0.k + x0.k};562 local x2 = {k: x1.k + x1.k};563 local x3 = {k: x2.k + x2.k};564 local x4 = {k: x3.k + x3.k};565 local x5 = {k: x4.k + x4.k};566 local x6 = {k: x5.k + x5.k};567 local x7 = {k: x6.k + x6.k};568 local x8 = {k: x7.k + x7.k};569 local x9 = {k: x8.k + x8.k};570 local x10 = {k: x9.k + x9.k};571 local x11 = {k: x10.k + x10.k};572 local x12 = {k: x11.k + x11.k};573 local x13 = {k: x12.k + x12.k};574 local x14 = {k: x13.k + x13.k};575 local x15 = {k: x14.k + x14.k};576 local x16 = {k: x15.k + x15.k};577 local x17 = {k: x16.k + x16.k};578 local x18 = {k: x17.k + x17.k};579 local x19 = {k: x18.k + x18.k};580 local x20 = {k: x19.k + x19.k};581 local x21 = {k: x20.k + x20.k};582 x21.k583 "#584 );585 }586 */587}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 /// Used for stack-overflows and stacktraces72 stack: RefCell<Vec<StackTraceElement>>,73 /// Contains file source codes and evaluated results for imports and pretty74 /// printing stacktraces75 files: RefCell<HashMap<PathBuf, FileData>>,76 globals: RefCell<HashMap<String, Val>>,7778 settings: EvaluationSettings,79}8081thread_local! {82 /// Contains state for currently executing file83 /// Global state is fine there84 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}100101/// Maintains stack trace and import resolution102#[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 /*411 /// Sanity checking, before trusting to another tests412 #[test]413 fn equality_operator() {414 assert_eval!("2 == 2");415 assert_eval_neg!("2 != 2");416 assert_eval!("2 != 3");417 assert_eval_neg!("2 == 3");418 assert_eval!("'Hello' == 'Hello'");419 assert_eval_neg!("'Hello' != 'Hello'");420 assert_eval!("'Hello' != 'World'");421 assert_eval_neg!("'Hello' == 'World'");422 }423424 #[test]425 fn math_evaluation() {426 assert_eval!("2 + 2 * 2 == 6");427 assert_eval!("3 + (2 + 2 * 2) == 9");428 }429430 #[test]431 fn string_concat() {432 assert_eval!("'Hello' + 'World' == 'HelloWorld'");433 assert_eval!("'Hello' * 3 == 'HelloHelloHello'");434 assert_eval!("'Hello' + 'World' * 3 == 'HelloWorldWorldWorld'");435 }436437 #[test]438 fn local() {439 assert_eval!("local a = 2; local b = 3; a + b == 5");440 assert_eval!("local a = 1, b = a + 1; a + b == 3");441 assert_eval!("local a = 1; local a = 2; a == 2");442 }443444 #[test]445 fn object_lazyness() {446 assert_json!("local a = {a:error 'test'}; {}", r#"{}"#);447 }448449 #[test]450 fn object_inheritance() {451 assert_json!("{a: self.b} + {b:3}", r#"{"a":3,"b":3}"#);452 }453454 #[test]455 fn test_object() {456 assert_json!("{a:2}", r#"{"a":2}"#);457 assert_json!("{a:2+2}", r#"{"a":4}"#);458 assert_json!("{a:2}+{b:2}", r#"{"a":2,"b":2}"#);459 assert_json!("{b:3}+{b:2}", r#"{"b":2}"#);460 assert_json!("{b:3}+{b+:2}", r#"{"b":5}"#);461 assert_json!("local test='a'; {[test]:2}", r#"{"a":2}"#);462 assert_json!(463 r#"464 {465 name: "Alice",466 welcome: "Hello " + self.name + "!",467 }468 "#,469 r#"{"name":"Alice","welcome":"Hello Alice!"}"#470 );471 assert_json!(472 r#"473 {474 name: "Alice",475 welcome: "Hello " + self.name + "!",476 } + {477 name: "Bob"478 }479 "#,480 r#"{"name":"Bob","welcome":"Hello Bob!"}"#481 );482 }483484 #[test]485 fn functions() {486 assert_json!(r#"local a = function(b, c = 2) b + c; a(2)"#, "4");487 assert_json!(488 r#"local a = function(b, c = "Dear") b + c + d, d = "World"; a("Hello")"#,489 r#""HelloDearWorld""#490 );491 }492493 #[test]494 fn local_methods() {495 assert_json!(r#"local a(b, c = 2) = b + c; a(2)"#, "4");496 assert_json!(497 r#"local a(b, c = "Dear") = b + c + d, d = "World"; a("Hello")"#,498 r#""HelloDearWorld""#499 );500 }501502 #[test]503 fn object_locals() {504 assert_json!(r#"{local a = 3, b: a}"#, r#"{"b":3}"#);505 assert_json!(r#"{local a = 3, local c = a, b: c}"#, r#"{"b":3}"#);506 assert_json!(507 r#"{local a = function (b) {[b]:4}, test: a("test")}"#,508 r#"{"test":{"test":4}}"#509 );510 }511512 #[test]513 fn direct_self() {514 println!(515 "{:#?}",516 eval!(517 r#"518 {519 local me = self,520 a: 3,521 b(): me.a,522 }523 "#524 )525 );526 }527528 #[test]529 fn indirect_self() {530 // `self` assigned to `me` was lost when being531 // referenced from field532 eval_stdlib!(533 r#"{534 local me = self,535 a: 3,536 b: me.a,537 }.b"#538 );539 }540541 // We can't trust other tests (And official jsonnet testsuite), if assert is not working correctly542 #[test]543 fn std_assert_ok() {544 eval_stdlib!("std.assertEqual(4.5 << 2, 16)");545 }546547 #[test]548 #[should_panic]549 fn std_assert_failure() {550 eval_stdlib!("std.assertEqual(4.5 << 2, 15)");551 }552553 #[test]554 fn string_is_string() {555 assert_eq!(556 eval_stdlib!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"),557 Val::Bool(false)558 );559 }560561 #[test]562 fn base64_works() {563 assert_json_stdlib!(r#"std.base64("test")"#, r#""dGVzdA==""#);564 }565566 #[test]567 fn utf8_chars() {568 assert_json_stdlib!(569 r#"local c="😎";{c:std.codepoint(c),l:std.length(c)}"#,570 r#"{"c":128526,"l":1}"#571 )572 }573574 #[test]575 fn json() {576 assert_json_stdlib!(577 r#"std.manifestJsonEx({a:3, b:4, c:6},"")"#,578 r#""{\n"a": 3,\n"b": 4,\n"c": 6\n}""#579 );580 }581582 #[test]583 fn test() {584 assert_json_stdlib!(585 r#"[[a, b] for a in [1,2,3] for b in [4,5,6]]"#,586 "[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]"587 );588 }589590 #[test]591 fn sjsonnet() {592 eval!(593 r#"594 local x0 = {k: 1};595 local x1 = {k: x0.k + x0.k};596 local x2 = {k: x1.k + x1.k};597 local x3 = {k: x2.k + x2.k};598 local x4 = {k: x3.k + x3.k};599 local x5 = {k: x4.k + x4.k};600 local x6 = {k: x5.k + x5.k};601 local x7 = {k: x6.k + x6.k};602 local x8 = {k: x7.k + x7.k};603 local x9 = {k: x8.k + x8.k};604 local x10 = {k: x9.k + x9.k};605 local x11 = {k: x10.k + x10.k};606 local x12 = {k: x11.k + x11.k};607 local x13 = {k: x12.k + x12.k};608 local x14 = {k: x13.k + x13.k};609 local x15 = {k: x14.k + x14.k};610 local x16 = {k: x15.k + x15.k};611 local x17 = {k: x16.k + x16.k};612 local x18 = {k: x17.k + x17.k};613 local x19 = {k: x18.k + x18.k};614 local x20 = {k: x19.k + x19.k};615 local x21 = {k: x20.k + x20.k};616 x21.k617 "#618 );619 }620 */621}crates/jsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/val.rs
+++ b/crates/jsonnet-evaluator/src/val.rs
@@ -78,6 +78,8 @@
}
impl FuncDesc {
// TODO: Check for unset variables
+ /// This function is always inlined to make tailstrict work
+ #[inline(always)]
pub fn evaluate(&self, args: Vec<(Option<String>, Val)>) -> Result<Val> {
let mut new_bindings: HashMap<String, LazyBinding> = HashMap::new();
let future_ctx = Context::new_future();