difftreelog
feat(evaluator) tailstrict functions
in: master
5 files changed
crates/jsonnet-evaluator/src/ctx.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/ctx.rs
+++ b/crates/jsonnet-evaluator/src/ctx.rs
@@ -1,8 +1,6 @@
use crate::{
- future_wrapper, lazy_binding, lazy_val, rc_fn_helper, LazyBinding, LazyVal, ObjValue, Result,
- Val,
+ future_wrapper, rc_fn_helper, resolved_lazy_val, LazyBinding, LazyVal, ObjValue, Result, Val,
};
-use closure::closure;
use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
rc_fn_helper!(
@@ -68,12 +66,7 @@
pub fn with_var(&self, name: String, value: Val) -> Result<Context> {
let mut new_bindings: HashMap<_, LazyBinding> = HashMap::new();
- new_bindings.insert(
- name,
- lazy_binding!(
- closure!(clone value, |_t, _s|Ok(lazy_val!(closure!(clone value, ||Ok(value.clone())))))
- ),
- );
+ new_bindings.insert(name, LazyBinding::Bound(resolved_lazy_val!(value.clone())));
self.extend(new_bindings, None, None, None)
}
@@ -95,7 +88,7 @@
new.insert(k.clone(), v.clone());
}
for (k, v) in new_bindings.into_iter() {
- new.insert(k, v.0(this.clone(), super_obj.clone())?);
+ new.insert(k, v.evaluate(this.clone(), super_obj.clone())?);
}
Rc::new(new)
};
crates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/evaluate.rs
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -1,7 +1,7 @@
use crate::{
binding, context_creator, create_error, function_default, function_rhs, future_wrapper,
- lazy_binding, lazy_val, push, Context, ContextCreator, FuncDesc, LazyBinding, ObjMember,
- ObjValue, Result, Val,
+ lazy_val, push, Context, ContextCreator, FuncDesc, LazyBinding, LazyVal, ObjMember, ObjValue,
+ Result, Val,
};
use closure::closure;
use jsonnet_parser::{
@@ -19,18 +19,20 @@
let args = args.clone();
(
b.name.clone(),
- lazy_binding!(move |this, super_obj| Ok(lazy_val!(
- closure!(clone b, clone args, clone context_creator, || Ok(evaluate_method(
- context_creator.0(this.clone(), super_obj.clone())?,
- &b.value,
- args.clone()
- )))
- ))),
+ LazyBinding::Bindable(Rc::new(move |this, super_obj| {
+ Ok(lazy_val!(
+ closure!(clone b, clone args, clone context_creator, || Ok(evaluate_method(
+ context_creator.0(this.clone(), super_obj.clone())?,
+ &b.value,
+ args.clone()
+ )))
+ ))
+ })),
)
} else {
(
b.name.clone(),
- lazy_binding!(move |this, super_obj| {
+ LazyBinding::Bindable(Rc::new(move |this, super_obj| {
Ok(lazy_val!(closure!(clone context_creator, clone b, ||
push(b.value.clone(), "thunk".to_owned(), ||{
evaluate(
@@ -39,7 +41,7 @@
)
})
)))
- }),
+ })),
)
}
}
@@ -405,7 +407,7 @@
&evaluate(context.clone(), s)?,
&Val::Obj(evaluate_object(context, t.clone())?),
)?,
- Apply(value, ArgsDesc(args)) => {
+ Apply(value, ArgsDesc(args), tailstrict) => {
let value = evaluate(context.clone(), value)?.unwrap_if_lazy()?;
match value {
Val::Intristic(ns, name) => match (&ns as &str, &name as &str) {
@@ -502,7 +504,8 @@
evaluate(context.clone(), &args[0].1)?,
evaluate(context, &args[1].1)?,
) {
- println!("{}", a);
+ // TODO: Line numbers as in original jsonnet
+ println!("TRACE: {}", a);
b
} else {
panic!("bad trace call");
@@ -517,9 +520,15 @@
.map(move |a| {
(
a.clone().0,
- Val::Lazy(lazy_val!(
- closure!(clone context, clone a, || evaluate(context.clone(), &a.clone().1))
- )),
+ if *tailstrict {
+ Val::Lazy(LazyVal::new_resolved(
+ evaluate(context.clone(), &a.1).unwrap(),
+ ))
+ } else {
+ Val::Lazy(lazy_val!(
+ closure!(clone context, clone a, || evaluate(context.clone(), &a.clone().1))
+ ))
+ },
)
})
.collect(),
crates/jsonnet-evaluator/src/function.rsdiffbeforeafterbothno changes
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 obj;10mod val;1112use closure::closure;13pub use ctx::*;14pub use dynamic::*;15pub use error::*;16pub use evaluate::*;17use jsonnet_parser::*;18pub use obj::*;19use std::{cell::RefCell, collections::HashMap, 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!(28 LazyBinding,29 lazy_binding,30 dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<LazyVal>31);32rc_fn_helper!(FunctionRhs, function_rhs, dyn Fn(Context) -> Result<Val>);33rc_fn_helper!(34 FunctionDefault,35 function_default,36 dyn Fn(Context, LocExpr) -> Result<Val>37);3839pub struct FileData(String, LocExpr, Option<Val>);40#[derive(Default)]41pub struct EvaluationStateInternals {42 /// Used for stack-overflows and stacktraces43 stack: RefCell<Vec<StackTraceElement>>,44 /// Contains file source codes and evaluated results for imports and pretty45 /// printing stacktraces46 files: RefCell<HashMap<PathBuf, FileData>>,47 globals: RefCell<HashMap<String, Val>>,48}4950thread_local! {51 pub static EVAL_STATE: RefCell<Option<EvaluationState>> = RefCell::new(None)52}53pub(crate) fn with_state<T>(f: impl FnOnce(&EvaluationState) -> T) -> T {54 EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap()))55}56pub(crate) fn create_error<T>(err: Error) -> Result<T> {57 with_state(|s| s.error(err))58}59pub(crate) fn push<T>(e: LocExpr, comment: String, f: impl FnOnce() -> Result<T>) -> Result<T> {60 with_state(|s| s.push(e, comment, f))61}6263#[derive(Default, Clone)]64pub struct EvaluationState(Rc<EvaluationStateInternals>);65impl EvaluationState {66 pub fn add_file(&self, name: PathBuf, code: String) -> std::result::Result<(), ParseError> {67 self.0.files.borrow_mut().insert(68 name.clone(),69 FileData(70 code.clone(),71 parse(72 &code,73 &ParserSettings {74 file_name: name,75 loc_data: true,76 },77 )?,78 None,79 ),80 );8182 Ok(())83 }84 pub fn add_parsed_file(85 &self,86 name: PathBuf,87 code: String,88 parsed: LocExpr,89 ) -> std::result::Result<(), ()> {90 self.091 .files92 .borrow_mut()93 .insert(name, FileData(code, parsed, None));9495 Ok(())96 }97 pub fn get_source(&self, name: &PathBuf) -> Option<String> {98 let ro_map = self.0.files.borrow();99 ro_map.get(name).map(|value| value.0.clone())100 }101 pub fn evaluate_file(&self, name: &PathBuf) -> Result<Val> {102 self.begin_state();103 let expr: LocExpr = {104 let ro_map = self.0.files.borrow();105 let value = ro_map106 .get(name)107 .unwrap_or_else(|| panic!("file not added: {:?}", name));108 if value.2.is_some() {109 return Ok(value.2.clone().unwrap());110 }111 value.1.clone()112 };113 let value = evaluate(self.create_default_context()?, &expr)?;114 {115 self.0116 .files117 .borrow_mut()118 .get_mut(name)119 .unwrap()120 .2121 .replace(value.clone());122 }123 self.end_state();124 Ok(value)125 }126127 pub fn parse_evaluate_raw(&self, code: &str) -> Result<Val> {128 let parsed = parse(129 &code,130 &ParserSettings {131 file_name: PathBuf::from("raw.jsonnet"),132 loc_data: true,133 },134 );135 self.begin_state();136 let value = evaluate(self.create_default_context()?, &parsed.unwrap());137 self.end_state();138 value139 }140141 pub fn add_global(&self, name: String, value: Val) {142 self.0.globals.borrow_mut().insert(name, value);143 }144145 pub fn add_stdlib(&self) {146 self.begin_state();147 use jsonnet_stdlib::STDLIB_STR;148 if cfg!(feature = "serialized-stdlib") {149 self.add_parsed_file(150 PathBuf::from("std.jsonnet"),151 STDLIB_STR.to_owned(),152 bincode::deserialize(include_bytes!(concat!(env!("OUT_DIR"), "/stdlib.bincode")))153 .expect("deserialize stdlib"),154 )155 .unwrap();156 } else {157 self.add_file(PathBuf::from("std.jsonnet"), STDLIB_STR.to_owned())158 .unwrap();159 }160 let val = self.evaluate_file(&PathBuf::from("std.jsonnet")).unwrap();161 self.add_global("std".to_owned(), val);162 self.end_state();163 }164165 pub fn create_default_context(&self) -> Result<Context> {166 let globals = self.0.globals.borrow();167 let mut new_bindings: HashMap<String, LazyBinding> = HashMap::new();168 for (name, value) in globals.iter() {169 new_bindings.insert(170 name.clone(),171 lazy_binding!(172 closure!(clone value, |_self, _super_obj| Ok(lazy_val!(closure!(clone value, ||Ok(value.clone())))))173 ),174 );175 }176 Context::new().extend(new_bindings, None, None, None)177 }178179 pub fn push<T>(&self, e: LocExpr, comment: String, f: impl FnOnce() -> Result<T>) -> Result<T> {180 {181 let mut stack = self.0.stack.borrow_mut();182 if stack.len() > 5000 {183 drop(stack);184 return self.error(Error::StackOverflow);185 } else {186 stack.push(StackTraceElement(e, comment));187 }188 }189 let result = f();190 self.0.stack.borrow_mut().pop();191 result192 }193 pub fn print_stack_trace(&self) {194 for e in self.stack_trace().0 {195 println!("{:?} - {:?}", e.0, e.1)196 }197 }198 pub fn stack_trace(&self) -> StackTrace {199 StackTrace(self.0.stack.borrow().iter().rev().cloned().collect())200 }201 pub fn error<T>(&self, err: Error) -> Result<T> {202 Err(LocError(err, self.stack_trace()))203 }204205 fn begin_state(&self) {206 EVAL_STATE.with(|v| v.borrow_mut().replace(self.clone()));207 }208 fn end_state(&self) {209 EVAL_STATE.with(|v| v.borrow_mut().take());210 }211}212213#[cfg(test)]214pub mod tests {215 use super::Val;216 use crate::EvaluationState;217 use jsonnet_parser::*;218 use std::path::PathBuf;219220 #[test]221 fn eval_state_stacktrace() {222 let state = EvaluationState::default();223 state224 .push(225 loc_expr!(226 Expr::Num(0.0),227 true,228 (PathBuf::from("test1.jsonnet"), 10, 20)229 ),230 "outer".to_owned(),231 || {232 state.push(233 loc_expr!(234 Expr::Num(0.0),235 true,236 (PathBuf::from("test2.jsonnet"), 30, 40)237 ),238 "inner".to_owned(),239 || {240 state.print_stack_trace();241 Ok(())242 },243 )?;244 Ok(())245 },246 )247 .unwrap();248 }249250 #[test]251 fn eval_state_standard() {252 let state = EvaluationState::default();253 state.add_stdlib();254 assert_eq!(255 state256 .parse_evaluate_raw(r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#)257 .unwrap(),258 Val::Bool(true)259 );260 }261262 macro_rules! eval {263 ($str: expr) => {264 evaluate(265 Context::new(),266 EvaluationState::default(),267 &parse(268 $str,269 &ParserSettings {270 loc_data: true,271 file_name: "test.jsonnet".to_owned(),272 },273 )274 .unwrap(),275 )276 };277 }278279 macro_rules! eval_stdlib {280 ($str: expr) => {{281 let std = "local std = ".to_owned() + jsonnet_stdlib::STDLIB_STR + ";";282 evaluate(283 Context::new(),284 EvaluationState::default(),285 &parse(286 &(std + $str),287 &ParserSettings {288 loc_data: true,289 file_name: "test.jsonnet".to_owned(),290 },291 )292 .unwrap(),293 )294 }};295 }296297 macro_rules! assert_eval {298 ($str: expr) => {299 assert_eq!(300 evaluate(301 Context::new(),302 EvaluationState::default(),303 &parse(304 $str,305 &ParserSettings {306 loc_data: true,307 file_name: "test.jsonnet".to_owned(),308 }309 )310 .unwrap()311 ),312 Val::Bool(true)313 )314 };315 }316 macro_rules! assert_json {317 ($str: expr, $out: expr) => {318 assert_eq!(319 format!(320 "{}",321 evaluate(322 Context::new(),323 EvaluationState::default(),324 &parse(325 $str,326 &ParserSettings {327 loc_data: true,328 file_name: "test.jsonnet".to_owned(),329 }330 )331 .unwrap()332 )333 ),334 $out335 )336 };337 }338 macro_rules! assert_json_stdlib {339 ($str: expr, $out: expr) => {340 assert_eq!(format!("{}", eval_stdlib!($str)), $out)341 };342 }343 macro_rules! assert_eval_neg {344 ($str: expr) => {345 assert_eq!(346 evaluate(347 Context::new(),348 EvaluationState::default(),349 &parse(350 $str,351 &ParserSettings {352 loc_data: true,353 file_name: "test.jsonnet".to_owned(),354 }355 )356 .unwrap()357 ),358 Val::Bool(false)359 )360 };361 }362363 /*364 /// Sanity checking, before trusting to another tests365 #[test]366 fn equality_operator() {367 assert_eval!("2 == 2");368 assert_eval_neg!("2 != 2");369 assert_eval!("2 != 3");370 assert_eval_neg!("2 == 3");371 assert_eval!("'Hello' == 'Hello'");372 assert_eval_neg!("'Hello' != 'Hello'");373 assert_eval!("'Hello' != 'World'");374 assert_eval_neg!("'Hello' == 'World'");375 }376377 #[test]378 fn math_evaluation() {379 assert_eval!("2 + 2 * 2 == 6");380 assert_eval!("3 + (2 + 2 * 2) == 9");381 }382383 #[test]384 fn string_concat() {385 assert_eval!("'Hello' + 'World' == 'HelloWorld'");386 assert_eval!("'Hello' * 3 == 'HelloHelloHello'");387 assert_eval!("'Hello' + 'World' * 3 == 'HelloWorldWorldWorld'");388 }389390 #[test]391 fn local() {392 assert_eval!("local a = 2; local b = 3; a + b == 5");393 assert_eval!("local a = 1, b = a + 1; a + b == 3");394 assert_eval!("local a = 1; local a = 2; a == 2");395 }396397 #[test]398 fn object_lazyness() {399 assert_json!("local a = {a:error 'test'}; {}", r#"{}"#);400 }401402 #[test]403 fn object_inheritance() {404 assert_json!("{a: self.b} + {b:3}", r#"{"a":3,"b":3}"#);405 }406407 #[test]408 fn test_object() {409 assert_json!("{a:2}", r#"{"a":2}"#);410 assert_json!("{a:2+2}", r#"{"a":4}"#);411 assert_json!("{a:2}+{b:2}", r#"{"a":2,"b":2}"#);412 assert_json!("{b:3}+{b:2}", r#"{"b":2}"#);413 assert_json!("{b:3}+{b+:2}", r#"{"b":5}"#);414 assert_json!("local test='a'; {[test]:2}", r#"{"a":2}"#);415 assert_json!(416 r#"417 {418 name: "Alice",419 welcome: "Hello " + self.name + "!",420 }421 "#,422 r#"{"name":"Alice","welcome":"Hello Alice!"}"#423 );424 assert_json!(425 r#"426 {427 name: "Alice",428 welcome: "Hello " + self.name + "!",429 } + {430 name: "Bob"431 }432 "#,433 r#"{"name":"Bob","welcome":"Hello Bob!"}"#434 );435 }436437 #[test]438 fn functions() {439 assert_json!(r#"local a = function(b, c = 2) b + c; a(2)"#, "4");440 assert_json!(441 r#"local a = function(b, c = "Dear") b + c + d, d = "World"; a("Hello")"#,442 r#""HelloDearWorld""#443 );444 }445446 #[test]447 fn local_methods() {448 assert_json!(r#"local a(b, c = 2) = b + c; a(2)"#, "4");449 assert_json!(450 r#"local a(b, c = "Dear") = b + c + d, d = "World"; a("Hello")"#,451 r#""HelloDearWorld""#452 );453 }454455 #[test]456 fn object_locals() {457 assert_json!(r#"{local a = 3, b: a}"#, r#"{"b":3}"#);458 assert_json!(r#"{local a = 3, local c = a, b: c}"#, r#"{"b":3}"#);459 assert_json!(460 r#"{local a = function (b) {[b]:4}, test: a("test")}"#,461 r#"{"test":{"test":4}}"#462 );463 }464465 #[test]466 fn direct_self() {467 println!(468 "{:#?}",469 eval!(470 r#"471 {472 local me = self,473 a: 3,474 b(): me.a,475 }476 "#477 )478 );479 }480481 #[test]482 fn indirect_self() {483 // `self` assigned to `me` was lost when being484 // referenced from field485 eval_stdlib!(486 r#"{487 local me = self,488 a: 3,489 b: me.a,490 }.b"#491 );492 }493494 // We can't trust other tests (And official jsonnet testsuite), if assert is not working correctly495 #[test]496 fn std_assert_ok() {497 eval_stdlib!("std.assertEqual(4.5 << 2, 16)");498 }499500 #[test]501 #[should_panic]502 fn std_assert_failure() {503 eval_stdlib!("std.assertEqual(4.5 << 2, 15)");504 }505506 #[test]507 fn string_is_string() {508 assert_eq!(509 eval_stdlib!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"),510 Val::Bool(false)511 );512 }513514 #[test]515 fn base64_works() {516 assert_json_stdlib!(r#"std.base64("test")"#, r#""dGVzdA==""#);517 }518519 #[test]520 fn utf8_chars() {521 assert_json_stdlib!(522 r#"local c="😎";{c:std.codepoint(c),l:std.length(c)}"#,523 r#"{"c":128526,"l":1}"#524 )525 }526527 #[test]528 fn json() {529 assert_json_stdlib!(530 r#"std.manifestJsonEx({a:3, b:4, c:6},"")"#,531 r#""{\n"a": 3,\n"b": 4,\n"c": 6\n}""#532 );533 }534535 #[test]536 fn test() {537 assert_json_stdlib!(538 r#"[[a, b] for a in [1,2,3] for b in [4,5,6]]"#,539 "[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]"540 );541 }542543 #[test]544 fn sjsonnet() {545 eval!(546 r#"547 local x0 = {k: 1};548 local x1 = {k: x0.k + x0.k};549 local x2 = {k: x1.k + x1.k};550 local x3 = {k: x2.k + x2.k};551 local x4 = {k: x3.k + x3.k};552 local x5 = {k: x4.k + x4.k};553 local x6 = {k: x5.k + x5.k};554 local x7 = {k: x6.k + x6.k};555 local x8 = {k: x7.k + x7.k};556 local x9 = {k: x8.k + x8.k};557 local x10 = {k: x9.k + x9.k};558 local x11 = {k: x10.k + x10.k};559 local x12 = {k: x11.k + x11.k};560 local x13 = {k: x12.k + x12.k};561 local x14 = {k: x13.k + x13.k};562 local x15 = {k: x14.k + x14.k};563 local x16 = {k: x15.k + x15.k};564 local x17 = {k: x16.k + x16.k};565 local x18 = {k: x17.k + x17.k};566 local x19 = {k: x18.k + x18.k};567 local x20 = {k: x19.k + x19.k};568 local x21 = {k: x20.k + x20.k};569 x21.k570 "#571 );572 }573 */574}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)]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}crates/jsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/val.rs
+++ b/crates/jsonnet-evaluator/src/val.rs
@@ -1,6 +1,5 @@
use crate::{
- create_error, lazy_binding, Context, Error, FunctionDefault, FunctionRhs, LazyBinding,
- ObjValue, Result,
+ create_error, Context, Error, FunctionDefault, FunctionRhs, LazyBinding, ObjValue, Result,
};
use closure::closure;
use jsonnet_parser::{Param, ParamsDesc};
@@ -12,7 +11,7 @@
};
struct LazyValInternals {
- pub f: Box<dyn Fn() -> Result<Val>>,
+ pub f: Option<Box<dyn Fn() -> Result<Val>>>,
pub cached: RefCell<Option<Val>>,
}
#[derive(Clone)]
@@ -20,10 +19,16 @@
impl LazyVal {
pub fn new(f: Box<dyn Fn() -> Result<Val>>) -> Self {
LazyVal(Rc::new(LazyValInternals {
- f,
+ f: Some(f),
cached: RefCell::new(None),
}))
}
+ pub fn new_resolved(val: Val) -> Self {
+ LazyVal(Rc::new(LazyValInternals {
+ f: None,
+ cached: RefCell::new(Some(val)),
+ }))
+ }
pub fn evaluate(&self) -> Result<Val> {
{
let cached = self.0.cached.borrow();
@@ -31,17 +36,24 @@
return Ok(cached.clone().unwrap());
}
}
- let result = (self.0.f)()?;
+ let result = (self.0.f.as_ref().unwrap())()?;
self.0.cached.borrow_mut().replace(result.clone());
Ok(result)
}
}
+
#[macro_export]
macro_rules! lazy_val {
($f: expr) => {
$crate::LazyVal::new(Box::new($f))
};
}
+#[macro_export]
+macro_rules! resolved_lazy_val {
+ ($f: expr) => {
+ $crate::LazyVal::new_resolved($f)
+ };
+}
impl Debug for LazyVal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.0.cached.borrow().is_some() {
@@ -75,25 +87,23 @@
let eval_default = self.eval_default.clone();
new_bindings.insert(
name,
- lazy_binding!(closure!(clone future_ctx, clone default, clone eval_default, |_, _| Ok(lazy_val!(closure!(clone future_ctx, clone eval_default, clone default, || (eval_default.clone()).0
- (future_ctx.clone().unwrap(), default.clone())))))),
+ LazyBinding::Bound(lazy_val!(
+ closure!(clone future_ctx, clone eval_default, clone default, || (eval_default.clone()).0
+ (future_ctx.clone().unwrap(), default.clone()))
+ )),
);
}
for (name, val) in args.clone().into_iter().filter(|e| e.0.is_some()) {
new_bindings.insert(
name.as_ref().unwrap().clone(),
- lazy_binding!(
- closure!(clone val, |_, _| Ok(lazy_val!(closure!(clone val, || Ok(val.clone())))))
- ),
+ LazyBinding::Bound(resolved_lazy_val!(val.clone())),
);
}
for (i, param) in self.params.0.iter().enumerate() {
if let Some((None, val)) = args.get(i) {
new_bindings.insert(
param.0.clone(),
- lazy_binding!(
- closure!(clone val, |_, _| Ok(lazy_val!(closure!(clone val, || Ok(val.clone())))))
- ),
+ LazyBinding::Bound(resolved_lazy_val!(val.clone())),
);
}
}