difftreelog
feat native callbacks
in: master
10 files changed
bindings/jsonnet/src/interop.rsdiffbeforeafterboth--- a/bindings/jsonnet/src/interop.rs
+++ b/bindings/jsonnet/src/interop.rs
@@ -1,7 +1,7 @@
//! Jrsonnet specific additional binding helpers
-use crate::import::jsonnet_import_callback;
-use jrsonnet_evaluator::EvaluationState;
+use crate::{import::jsonnet_import_callback, native::jsonnet_native_callback};
+use jrsonnet_evaluator::{EvaluationState, Val};
use std::{
ffi::c_void,
os::raw::{c_char, c_int},
@@ -15,6 +15,13 @@
found_here: *mut *const c_char,
success: &mut c_int,
) -> *const c_char;
+
+ #[allow(improper_ctypes)]
+ pub fn _jrsonnet_static_native_callback(
+ ctx: *const c_void,
+ argv: *const *const Val,
+ success: *mut c_int,
+ ) -> *mut Val;
}
/// # Safety
@@ -26,6 +33,17 @@
jsonnet_import_callback(vm, _jrsonnet_static_import_callback, ctx)
}
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn jrsonnet_apply_static_native_callback(
+ vm: &EvaluationState,
+ name: *const c_char,
+ ctx: *mut c_void,
+ raw_params: *const *const c_char,
+) {
+ jsonnet_native_callback(vm, name, _jrsonnet_static_native_callback, ctx, raw_params)
+}
+
#[no_mangle]
pub extern "C" fn jrsonnet_set_trace_format(vm: &EvaluationState, format: u8) {
use jrsonnet_evaluator::trace::JSFormat;
bindings/jsonnet/src/lib.rsdiffbeforeafterboth--- a/bindings/jsonnet/src/lib.rs
+++ b/bindings/jsonnet/src/lib.rs
@@ -1,5 +1,6 @@
pub mod import;
pub mod interop;
+pub mod native;
pub mod val_extract;
pub mod val_make;
pub mod val_modify;
@@ -90,11 +91,6 @@
#[allow(clippy::boxed_local)]
pub unsafe extern "C" fn jsonnet_json_destroy(_vm: &EvaluationState, v: *mut Val) {
Box::from_raw(v);
-}
-
-#[no_mangle]
-pub extern "C" fn jsonnet_native_callback() {
- todo!()
}
#[no_mangle]
bindings/jsonnet/src/native.rsdiffbeforeafterboth--- /dev/null
+++ b/bindings/jsonnet/src/native.rs
@@ -0,0 +1,55 @@
+use jrsonnet_evaluator::{error::Error, native::NativeCallback, EvaluationState, Val};
+use jrsonnet_parser::{Param, ParamsDesc};
+use std::{
+ ffi::{c_void, CStr},
+ os::raw::{c_char, c_int},
+ rc::Rc,
+};
+
+type JsonnetNativeCallback = unsafe extern "C" fn(
+ ctx: *const c_void,
+ argv: *const *const Val,
+ success: *mut c_int,
+) -> *mut Val;
+
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn jsonnet_native_callback(
+ vm: &EvaluationState,
+ name: *const c_char,
+ cb: JsonnetNativeCallback,
+ ctx: *const c_void,
+ mut raw_params: *const *const c_char,
+) {
+ let name = CStr::from_ptr(name).to_str().expect("utf8 name").into();
+ let mut params = Vec::new();
+ loop {
+ if (*raw_params).is_null() {
+ break;
+ }
+ let param = CStr::from_ptr(*raw_params).to_str().expect("not utf8");
+ params.push(Param(param.into(), None));
+ raw_params = raw_params.offset(1);
+ }
+ let params = ParamsDesc(Rc::new(params));
+
+ vm.add_native(
+ name,
+ Rc::new(NativeCallback::new(params, move |args| {
+ let mut n_args = Vec::new();
+ for a in args {
+ n_args.push(Some(Box::new(a.clone())));
+ }
+ n_args.push(None);
+ let mut success = 1;
+ let v = cb(ctx, &n_args as *const _ as *const *const Val, &mut success);
+ let v = *Box::from_raw(v);
+ if success == 1 {
+ Ok(v)
+ } else {
+ let e = v.try_cast_str("native error").expect("error msg");
+ Err(Error::RuntimeError(e).into())
+ }
+ })),
+ )
+}
crates/jrsonnet-evaluator/src/builtin/manifest.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/manifest.rs
@@ -117,7 +117,7 @@
}
buf.push('}');
}
- Val::Func(_) | Val::Intristic(_, _) => {
+ Val::Func(_) | Val::Intristic(_, _) | Val::NativeExt(_, _) => {
throw!(RuntimeError("tried to manifest function".into()))
}
Val::Lazy(_) => unreachable!(),
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -33,6 +33,7 @@
FunctionParameterNotBoundInCall(Rc<str>),
UndefinedExternalVariable(Rc<str>),
+ UndefinedExternalFunction(Rc<str>),
FieldMustBeStringGot(ValType),
crates/jrsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate.rs
@@ -5,9 +5,9 @@
},
context_creator, equals,
error::Error::*,
- future_wrapper, lazy_val, parse_args, primitive_equals, push, throw, with_state, Context,
- ContextCreator, FuncDesc, LazyBinding, LazyVal, LocError, ObjMember, ObjValue, Result, Val,
- ValType,
+ future_wrapper, lazy_val, parse_args, parse_function_call, primitive_equals, push, throw,
+ with_state, Context, ContextCreator, FuncDesc, LazyBinding, LazyVal, LocError, ObjMember,
+ ObjValue, Result, Val, ValType,
};
use closure::closure;
use jrsonnet_parser::{
@@ -549,13 +549,20 @@
], {
Ok(Val::Num(x.powf(n)))
})?,
- ("std", "extVar") => parse_args!(context, "std.extVar", args, 2, [
+ ("std", "extVar") => parse_args!(context, "std.extVar", args, 1, [
0, x: [Val::Str]!!Val::Str, vec![ValType::Str];
], {
Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or_else(
|| UndefinedExternalVariable(x),
)?)
})?,
+ ("std", "native") => parse_args!(context, "std.native", args, 1, [
+ 0, x: [Val::Str]!!Val::Str, vec![ValType::Str];
+ ], {
+ Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::NativeExt(x.clone(), v)).ok_or_else(
+ || UndefinedExternalFunction(x),
+ )?)
+ })?,
("std", "filter") => noinline!(parse_args!(context, "std.filter", args, 2, [
0, func: [Val::Func]!!Val::Func, vec![ValType::Func];
1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];
@@ -780,6 +787,18 @@
})
},
)?,
+ Val::NativeExt(n, f) => push(
+ loc,
+ || format!("native <{}> call", n),
+ || {
+ let args = parse_function_call(context, None, &f.params, args, true)?;
+ let mut out_args = Vec::with_capacity(f.params.len());
+ for p in f.params.0.iter() {
+ out_args.push(args.binding(p.0.clone())?.evaluate()?);
+ }
+ Ok(f.call(&out_args)?)
+ },
+ )?,
Val::Func(f) => {
let body = || f.evaluate(context, args, tailstrict);
if tailstrict {
crates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -40,7 +40,7 @@
}
Value::Object(out)
}
- Val::Func(_) | Val::Intristic(_, _) => {
+ Val::Func(_) | Val::Intristic(_, _) | Val::NativeExt(_, _) => {
throw!(RuntimeError("tried to manifest function".into()))
}
})
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -10,6 +10,7 @@
mod import;
mod integrations;
mod map;
+pub mod native;
mod obj;
pub mod trace;
mod val;
@@ -21,6 +22,7 @@
pub use function::parse_function_call;
pub use import::*;
use jrsonnet_parser::*;
+use native::NativeCallback;
pub use obj::*;
use std::{
cell::{Ref, RefCell, RefMut},
@@ -60,6 +62,8 @@
pub max_trace: usize,
/// Used for std.extVar
pub ext_vars: HashMap<Rc<str>, Val>,
+ /// Used for ext.native
+ pub ext_natives: HashMap<Rc<str>, Rc<NativeCallback>>,
/// TLA vars
pub tla_vars: HashMap<Rc<str>, Val>,
/// Global variables are inserted in default context
@@ -78,6 +82,7 @@
max_trace: 20,
globals: Default::default(),
ext_vars: Default::default(),
+ ext_natives: Default::default(),
tla_vars: Default::default(),
import_resolver: Box::new(DummyImportResolver),
manifest_format: ManifestFormat::Json(4),
@@ -415,6 +420,10 @@
self.settings_mut().import_resolver = resolver;
}
+ pub fn add_native(&self, name: Rc<str>, cb: Rc<NativeCallback>) {
+ self.settings_mut().ext_natives.insert(name, cb);
+ }
+
pub fn manifest_format(&self) -> ManifestFormat {
self.settings().manifest_format.clone()
}
@@ -850,4 +859,30 @@
);
assert_eval!("{ x: 1, y: 2 } == { x: 1, y: 2 }")
}
+
+ #[test]
+ fn native_ext() -> crate::error::Result<()> {
+ use super::native::NativeCallback;
+ let evaluator = EvaluationState::default();
+
+ evaluator.with_stdlib();
+ evaluator.settings_mut().ext_natives.insert(
+ "native_add".into(),
+ Rc::new(NativeCallback::new(
+ ParamsDesc(Rc::new(vec![
+ Param("a".into(), None),
+ Param("b".into(), None),
+ ])),
+ |args| match (&args[0], &args[1]) {
+ (Val::Num(a), Val::Num(b)) => Ok(Val::Num(a + b)),
+ (_, _) => todo!(),
+ },
+ )),
+ );
+ evaluator.evaluate_snippet_raw(
+ Rc::new(PathBuf::from("test.jsonnet")),
+ "std.assertEqual(std.native(\"native_add\")(1, 2), 3)".into(),
+ )?;
+ Ok(())
+ }
}
crates/jrsonnet-evaluator/src/native.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/native.rs
@@ -0,0 +1,24 @@
+use crate::{error::Result, Val};
+use jrsonnet_parser::ParamsDesc;
+use std::fmt::Debug;
+
+pub struct NativeCallback {
+ pub params: ParamsDesc,
+ handler: Box<dyn Fn(&[Val]) -> Result<Val>>,
+}
+impl NativeCallback {
+ pub fn new(params: ParamsDesc, handler: impl Fn(&[Val]) -> Result<Val> + 'static) -> Self {
+ Self {
+ params,
+ handler: Box::new(handler),
+ }
+ }
+ pub fn call(&self, args: &[Val]) -> Result<Val> {
+ (self.handler)(args)
+ }
+}
+impl Debug for NativeCallback {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("NativeCallback").finish()
+ }
+}
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use crate::{2 builtin::manifest::{manifest_json_ex, ManifestJsonOptions, ManifestType},3 error::Error::*,4 evaluate,5 function::{parse_function_call, parse_function_call_map, place_args},6 throw, with_state, Context, ObjValue, Result,7};8use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParamsDesc};9use std::{10 cell::RefCell,11 collections::HashMap,12 fmt::{Debug, Display},13 rc::Rc,14};1516enum LazyValInternals {17 Computed(Val),18 Waiting(Box<dyn Fn() -> Result<Val>>),19}20#[derive(Clone)]21pub struct LazyVal(Rc<RefCell<LazyValInternals>>);22impl LazyVal {23 pub fn new(f: Box<dyn Fn() -> Result<Val>>) -> Self {24 LazyVal(Rc::new(RefCell::new(LazyValInternals::Waiting(f))))25 }26 pub fn new_resolved(val: Val) -> Self {27 LazyVal(Rc::new(RefCell::new(LazyValInternals::Computed(val))))28 }29 pub fn evaluate(&self) -> Result<Val> {30 let new_value = match &*self.0.borrow() {31 LazyValInternals::Computed(v) => return Ok(v.clone()),32 LazyValInternals::Waiting(f) => f()?,33 };34 *self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone());35 Ok(new_value)36 }37}3839#[macro_export]40macro_rules! lazy_val {41 ($f: expr) => {42 $crate::LazyVal::new(Box::new($f))43 };44}45#[macro_export]46macro_rules! resolved_lazy_val {47 ($f: expr) => {48 $crate::LazyVal::new_resolved($f)49 };50}51impl Debug for LazyVal {52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {53 write!(f, "Lazy")54 }55}56impl PartialEq for LazyVal {57 fn eq(&self, other: &Self) -> bool {58 Rc::ptr_eq(&self.0, &other.0)59 }60}6162#[derive(Debug, PartialEq)]63pub struct FuncDesc {64 pub name: Rc<str>,65 pub ctx: Context,66 pub params: ParamsDesc,67 pub body: LocExpr,68}69impl FuncDesc {70 /// This function is always inlined to make tailstrict work71 pub fn evaluate(&self, call_ctx: Context, args: &ArgsDesc, tailstrict: bool) -> Result<Val> {72 let ctx = parse_function_call(73 call_ctx,74 Some(self.ctx.clone()),75 &self.params,76 args,77 tailstrict,78 )?;79 evaluate(ctx, &self.body)80 }8182 pub fn evaluate_map(83 &self,84 call_ctx: Context,85 args: &HashMap<Rc<str>, Val>,86 tailstrict: bool,87 ) -> Result<Val> {88 let ctx = parse_function_call_map(89 call_ctx,90 Some(self.ctx.clone()),91 &self.params,92 args,93 tailstrict,94 )?;95 evaluate(ctx, &self.body)96 }9798 pub fn evaluate_values(&self, call_ctx: Context, args: &[Val]) -> Result<Val> {99 let ctx = place_args(call_ctx, Some(self.ctx.clone()), &self.params, args)?;100 evaluate(ctx, &self.body)101 }102}103104#[derive(Debug, Clone, Copy, PartialEq)]105pub enum ValType {106 Bool,107 Null,108 Str,109 Num,110 Arr,111 Obj,112 Func,113}114impl ValType {115 pub fn name(&self) -> &'static str {116 use ValType::*;117 match self {118 Bool => "boolean",119 Null => "null",120 Str => "string",121 Num => "number",122 Arr => "array",123 Obj => "object",124 Func => "function",125 }126 }127}128impl Display for ValType {129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {130 write!(f, "{}", self.name())131 }132}133134#[derive(Clone)]135pub enum ManifestFormat {136 YamlStream(Box<ManifestFormat>),137 Yaml(usize),138 Json(usize),139 String,140}141142#[derive(Debug, Clone)]143pub enum Val {144 Bool(bool),145 Null,146 Str(Rc<str>),147 Num(f64),148 Lazy(LazyVal),149 Arr(Rc<Vec<Val>>),150 Obj(ObjValue),151 Func(Rc<FuncDesc>),152153 // Library functions implemented in native154 Intristic(Rc<str>, Rc<str>),155}156macro_rules! matches_unwrap {157 ($e: expr, $p: pat, $r: expr) => {158 match $e {159 $p => $r,160 _ => panic!("no match"),161 }162 };163}164impl Val {165 /// Creates Val::Num after checking for overflow. As numbers are f64, we can just check for finity166 pub fn new_checked_num(num: f64) -> Result<Val> {167 if num.is_finite() {168 Ok(Val::Num(num))169 } else {170 throw!(RuntimeError("overflow".into()))171 }172 }173174 pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {175 let this_type = self.value_type()?;176 if this_type != val_type {177 throw!(TypeMismatch(context, vec![val_type], this_type))178 } else {179 Ok(())180 }181 }182 pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {183 self.assert_type(context, ValType::Bool)?;184 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Bool(v), v))185 }186 pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {187 self.assert_type(context, ValType::Str)?;188 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Str(v), v))189 }190 pub fn try_cast_num(self, context: &'static str) -> Result<f64> {191 self.assert_type(context, ValType::Num)?;192 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Num(v), v))193 }194 pub fn unwrap_if_lazy(&self) -> Result<Self> {195 Ok(if let Val::Lazy(v) = self {196 v.evaluate()?.unwrap_if_lazy()?197 } else {198 self.clone()199 })200 }201 pub fn value_type(&self) -> Result<ValType> {202 Ok(match self {203 Val::Str(..) => ValType::Str,204 Val::Num(..) => ValType::Num,205 Val::Arr(..) => ValType::Arr,206 Val::Obj(..) => ValType::Obj,207 Val::Func(..) => ValType::Func,208 Val::Bool(_) => ValType::Bool,209 Val::Null => ValType::Null,210 Val::Intristic(_, _) => ValType::Func,211 Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,212 })213 }214215 pub fn into_string(self) -> Result<Rc<str>> {216 Ok(match self.unwrap_if_lazy()? {217 Val::Bool(true) => "true".into(),218 Val::Bool(false) => "false".into(),219 Val::Null => "null".into(),220 Val::Str(s) => s,221 v => manifest_json_ex(222 &v,223 &ManifestJsonOptions {224 padding: &"",225 mtype: ManifestType::ToString,226 },227 )?228 .into(),229 })230 }231232233 pub fn manifest(&self, ty: &ManifestFormat) -> Result<Rc<str>> {234 Ok(match ty {235 ManifestFormat::YamlStream(format) => {236 let arr = match self {237 Val::Arr(a) => a,238 _ => throw!(StreamManifestOutputIsNotAArray),239 };240 let mut out = String::new();241242 match format as &ManifestFormat {243 ManifestFormat::YamlStream(_) => throw!(StreamManifestOutputCannotBeRecursed),244 ManifestFormat::String => throw!(StreamManifestCannotNestString),245 _ => {}246 };247248 if !arr.is_empty() {249 for v in arr.iter() {250 out.push_str("---\n");251 out.push_str(&v.manifest(format)?);252 out.push_str("\n");253 }254 out.push_str("...");255 }256257 out.into()258 }259 ManifestFormat::Yaml(padding) => self.to_yaml(*padding)?,260 ManifestFormat::Json(padding) => self.to_json(*padding)?,261 ManifestFormat::String => match self {262 Val::Str(s) => s.clone(),263 _ => throw!(StringManifestOutputIsNotAString),264 },265 })266 }267268 /// For manifestification269 pub fn to_json(&self, padding: usize) -> Result<Rc<str>> {270 manifest_json_ex(271 self,272 &ManifestJsonOptions {273 padding: &" ".repeat(padding),274 mtype: if padding == 0 {275 ManifestType::Minify276 } else {277 ManifestType::Manifest278 },279 },280 )281 .map(|s| s.into())282 }283284 /// Calls std.manifestJson285 #[cfg(feature = "faster")]286 pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {287 manifest_json_ex(288 &self,289 &ManifestJsonOptions {290 padding: &" ".repeat(padding),291 mtype: ManifestType::Std,292 },293 )294 .map(|s| s.into())295 }296297 /// Calls std.manifestJson298 #[cfg(not(feature = "faster"))]299 pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {300 with_state(|s| {301 let ctx = s302 .create_default_context()?303 .with_var("__tmp__to_json__".into(), self.clone())?;304 Ok(evaluate(305 ctx,306 &el!(Expr::Apply(307 el!(Expr::Index(308 el!(Expr::Var("std".into())),309 el!(Expr::Str("manifestJsonEx".into()))310 )),311 ArgsDesc(vec![312 Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),313 Arg(None, el!(Expr::Str(" ".repeat(padding).into())))314 ]),315 false316 )),317 )?318 .try_cast_str("to json")?)319 })320 }321 pub fn to_yaml(&self, padding: usize) -> Result<Rc<str>> {322 with_state(|s| {323 let ctx = s324 .create_default_context()?325 .with_var("__tmp__to_json__".into(), self.clone());326 Ok(evaluate(327 ctx,328 &el!(Expr::Apply(329 el!(Expr::Index(330 el!(Expr::Var("std".into())),331 el!(Expr::Str("manifestYamlDoc".into()))332 )),333 ArgsDesc(vec![334 Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),335 Arg(None, el!(Expr::Str(" ".repeat(padding).into())))336 ]),337 false338 )),339 )?340 .try_cast_str("to json")?)341 })342 }343}344345fn is_function_like(val: &Val) -> bool {346 matches!(val, Val::Func(_) | Val::Intristic(_, _))347}348349/// Implements std.primitiveEquals builtin350pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {351 Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {352 (Val::Bool(a), Val::Bool(b)) => a == b,353 (Val::Null, Val::Null) => true,354 (Val::Str(a), Val::Str(b)) => a == b,355 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,356 (Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(357 "primitiveEquals operates on primitive types, got array".into(),358 )),359 (Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(360 "primitiveEquals operates on primitive types, got object".into(),361 )),362 (a, b) if is_function_like(&a) && is_function_like(&b) => {363 throw!(RuntimeError("cannot test equality of functions".into()))364 }365 (_, _) => false,366 })367}368369/// Native implementation of std.equals370pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {371 let val_a = val_a.unwrap_if_lazy()?;372 let val_b = val_b.unwrap_if_lazy()?;373374 if val_a.value_type()? != val_b.value_type()? {375 return Ok(false);376 }377 match (val_a, val_b) {378 // Cant test for ptr equality, because all fields needs to be evaluated379 (Val::Arr(a), Val::Arr(b)) => {380 if a.len() != b.len() {381 return Ok(false);382 }383 for (a, b) in a.iter().zip(b.iter()) {384 if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {385 return Ok(false);386 }387 }388 Ok(true)389 }390 (Val::Obj(a), Val::Obj(b)) => {391 let fields = a.visible_fields();392 if fields != b.visible_fields() {393 return Ok(false);394 }395 for field in fields {396 if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {397 return Ok(false);398 }399 }400 Ok(true)401 }402 (a, b) => Ok(primitive_equals(&a, &b)?),403 }404}1use crate::{2 builtin::manifest::{manifest_json_ex, ManifestJsonOptions, ManifestType},3 error::Error::*,4 evaluate,5 function::{parse_function_call, parse_function_call_map, place_args},6 native::NativeCallback,7 throw, with_state, Context, ObjValue, Result,8};9use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, LocExpr, ParamsDesc};10use std::{11 cell::RefCell,12 collections::HashMap,13 fmt::{Debug, Display},14 rc::Rc,15};1617enum LazyValInternals {18 Computed(Val),19 Waiting(Box<dyn Fn() -> Result<Val>>),20}21#[derive(Clone)]22pub struct LazyVal(Rc<RefCell<LazyValInternals>>);23impl LazyVal {24 pub fn new(f: Box<dyn Fn() -> Result<Val>>) -> Self {25 LazyVal(Rc::new(RefCell::new(LazyValInternals::Waiting(f))))26 }27 pub fn new_resolved(val: Val) -> Self {28 LazyVal(Rc::new(RefCell::new(LazyValInternals::Computed(val))))29 }30 pub fn evaluate(&self) -> Result<Val> {31 let new_value = match &*self.0.borrow() {32 LazyValInternals::Computed(v) => return Ok(v.clone()),33 LazyValInternals::Waiting(f) => f()?,34 };35 *self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone());36 Ok(new_value)37 }38}3940#[macro_export]41macro_rules! lazy_val {42 ($f: expr) => {43 $crate::LazyVal::new(Box::new($f))44 };45}46#[macro_export]47macro_rules! resolved_lazy_val {48 ($f: expr) => {49 $crate::LazyVal::new_resolved($f)50 };51}52impl Debug for LazyVal {53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {54 write!(f, "Lazy")55 }56}57impl PartialEq for LazyVal {58 fn eq(&self, other: &Self) -> bool {59 Rc::ptr_eq(&self.0, &other.0)60 }61}6263#[derive(Debug, PartialEq)]64pub struct FuncDesc {65 pub name: Rc<str>,66 pub ctx: Context,67 pub params: ParamsDesc,68 pub body: LocExpr,69}70impl FuncDesc {71 /// This function is always inlined to make tailstrict work72 pub fn evaluate(&self, call_ctx: Context, args: &ArgsDesc, tailstrict: bool) -> Result<Val> {73 let ctx = parse_function_call(74 call_ctx,75 Some(self.ctx.clone()),76 &self.params,77 args,78 tailstrict,79 )?;80 evaluate(ctx, &self.body)81 }8283 pub fn evaluate_map(84 &self,85 call_ctx: Context,86 args: &HashMap<Rc<str>, Val>,87 tailstrict: bool,88 ) -> Result<Val> {89 let ctx = parse_function_call_map(90 call_ctx,91 Some(self.ctx.clone()),92 &self.params,93 args,94 tailstrict,95 )?;96 evaluate(ctx, &self.body)97 }9899 pub fn evaluate_values(&self, call_ctx: Context, args: &[Val]) -> Result<Val> {100 let ctx = place_args(call_ctx, Some(self.ctx.clone()), &self.params, args)?;101 evaluate(ctx, &self.body)102 }103}104105#[derive(Debug, Clone, Copy, PartialEq)]106pub enum ValType {107 Bool,108 Null,109 Str,110 Num,111 Arr,112 Obj,113 Func,114}115impl ValType {116 pub fn name(&self) -> &'static str {117 use ValType::*;118 match self {119 Bool => "boolean",120 Null => "null",121 Str => "string",122 Num => "number",123 Arr => "array",124 Obj => "object",125 Func => "function",126 }127 }128}129impl Display for ValType {130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {131 write!(f, "{}", self.name())132 }133}134135#[derive(Clone)]136pub enum ManifestFormat {137 YamlStream(Box<ManifestFormat>),138 Yaml(usize),139 Json(usize),140 String,141}142143#[derive(Debug, Clone)]144pub enum Val {145 Bool(bool),146 Null,147 Str(Rc<str>),148 Num(f64),149 Lazy(LazyVal),150 Arr(Rc<Vec<Val>>),151 Obj(ObjValue),152 Func(Rc<FuncDesc>),153154 // Library functions implemented in native155 Intristic(Rc<str>, Rc<str>),156 NativeExt(Rc<str>, Rc<NativeCallback>),157}158macro_rules! matches_unwrap {159 ($e: expr, $p: pat, $r: expr) => {160 match $e {161 $p => $r,162 _ => panic!("no match"),163 }164 };165}166impl Val {167 /// Creates Val::Num after checking for overflow. As numbers are f64, we can just check for finity168 pub fn new_checked_num(num: f64) -> Result<Val> {169 if num.is_finite() {170 Ok(Val::Num(num))171 } else {172 throw!(RuntimeError("overflow".into()))173 }174 }175176 pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {177 let this_type = self.value_type()?;178 if this_type != val_type {179 throw!(TypeMismatch(context, vec![val_type], this_type))180 } else {181 Ok(())182 }183 }184 pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {185 self.assert_type(context, ValType::Bool)?;186 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Bool(v), v))187 }188 pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {189 self.assert_type(context, ValType::Str)?;190 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Str(v), v))191 }192 pub fn try_cast_num(self, context: &'static str) -> Result<f64> {193 self.assert_type(context, ValType::Num)?;194 Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Num(v), v))195 }196 pub fn unwrap_if_lazy(&self) -> Result<Self> {197 Ok(if let Val::Lazy(v) = self {198 v.evaluate()?.unwrap_if_lazy()?199 } else {200 self.clone()201 })202 }203 pub fn value_type(&self) -> Result<ValType> {204 Ok(match self {205 Val::Str(..) => ValType::Str,206 Val::Num(..) => ValType::Num,207 Val::Arr(..) => ValType::Arr,208 Val::Obj(..) => ValType::Obj,209 Val::Bool(_) => ValType::Bool,210 Val::Null => ValType::Null,211 Val::Func(..) | Val::Intristic(_, _) | Val::NativeExt(_, _) => ValType::Func,212 Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,213 })214 }215216 pub fn into_string(self) -> Result<Rc<str>> {217 Ok(match self.unwrap_if_lazy()? {218 Val::Bool(true) => "true".into(),219 Val::Bool(false) => "false".into(),220 Val::Null => "null".into(),221 Val::Str(s) => s,222 v => manifest_json_ex(223 &v,224 &ManifestJsonOptions {225 padding: &"",226 mtype: ManifestType::ToString,227 },228 )?229 .into(),230 })231 }232233234 pub fn manifest(&self, ty: &ManifestFormat) -> Result<Rc<str>> {235 Ok(match ty {236 ManifestFormat::YamlStream(format) => {237 let arr = match self {238 Val::Arr(a) => a,239 _ => throw!(StreamManifestOutputIsNotAArray),240 };241 let mut out = String::new();242243 match format as &ManifestFormat {244 ManifestFormat::YamlStream(_) => throw!(StreamManifestOutputCannotBeRecursed),245 ManifestFormat::String => throw!(StreamManifestCannotNestString),246 _ => {}247 };248249 if !arr.is_empty() {250 for v in arr.iter() {251 out.push_str("---\n");252 out.push_str(&v.manifest(format)?);253 out.push_str("\n");254 }255 out.push_str("...");256 }257258 out.into()259 }260 ManifestFormat::Yaml(padding) => self.to_yaml(*padding)?,261 ManifestFormat::Json(padding) => self.to_json(*padding)?,262 ManifestFormat::String => match self {263 Val::Str(s) => s.clone(),264 _ => throw!(StringManifestOutputIsNotAString),265 },266 })267 }268269 /// For manifestification270 pub fn to_json(&self, padding: usize) -> Result<Rc<str>> {271 manifest_json_ex(272 self,273 &ManifestJsonOptions {274 padding: &" ".repeat(padding),275 mtype: if padding == 0 {276 ManifestType::Minify277 } else {278 ManifestType::Manifest279 },280 },281 )282 .map(|s| s.into())283 }284285 /// Calls std.manifestJson286 #[cfg(feature = "faster")]287 pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {288 manifest_json_ex(289 &self,290 &ManifestJsonOptions {291 padding: &" ".repeat(padding),292 mtype: ManifestType::Std,293 },294 )295 .map(|s| s.into())296 }297298 /// Calls std.manifestJson299 #[cfg(not(feature = "faster"))]300 pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {301 with_state(|s| {302 let ctx = s303 .create_default_context()?304 .with_var("__tmp__to_json__".into(), self.clone())?;305 Ok(evaluate(306 ctx,307 &el!(Expr::Apply(308 el!(Expr::Index(309 el!(Expr::Var("std".into())),310 el!(Expr::Str("manifestJsonEx".into()))311 )),312 ArgsDesc(vec![313 Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),314 Arg(None, el!(Expr::Str(" ".repeat(padding).into())))315 ]),316 false317 )),318 )?319 .try_cast_str("to json")?)320 })321 }322 pub fn to_yaml(&self, padding: usize) -> Result<Rc<str>> {323 with_state(|s| {324 let ctx = s325 .create_default_context()?326 .with_var("__tmp__to_json__".into(), self.clone());327 Ok(evaluate(328 ctx,329 &el!(Expr::Apply(330 el!(Expr::Index(331 el!(Expr::Var("std".into())),332 el!(Expr::Str("manifestYamlDoc".into()))333 )),334 ArgsDesc(vec![335 Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),336 Arg(None, el!(Expr::Str(" ".repeat(padding).into())))337 ]),338 false339 )),340 )?341 .try_cast_str("to json")?)342 })343 }344}345346fn is_function_like(val: &Val) -> bool {347 matches!(val, Val::Func(_) | Val::Intristic(_, _) | Val::NativeExt(_, _))348}349350/// Implements std.primitiveEquals builtin351pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {352 Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {353 (Val::Bool(a), Val::Bool(b)) => a == b,354 (Val::Null, Val::Null) => true,355 (Val::Str(a), Val::Str(b)) => a == b,356 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,357 (Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(358 "primitiveEquals operates on primitive types, got array".into(),359 )),360 (Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(361 "primitiveEquals operates on primitive types, got object".into(),362 )),363 (a, b) if is_function_like(&a) && is_function_like(&b) => {364 throw!(RuntimeError("cannot test equality of functions".into()))365 }366 (_, _) => false,367 })368}369370/// Native implementation of std.equals371pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {372 let val_a = val_a.unwrap_if_lazy()?;373 let val_b = val_b.unwrap_if_lazy()?;374375 if val_a.value_type()? != val_b.value_type()? {376 return Ok(false);377 }378 match (val_a, val_b) {379 // Cant test for ptr equality, because all fields needs to be evaluated380 (Val::Arr(a), Val::Arr(b)) => {381 if a.len() != b.len() {382 return Ok(false);383 }384 for (a, b) in a.iter().zip(b.iter()) {385 if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {386 return Ok(false);387 }388 }389 Ok(true)390 }391 (Val::Obj(a), Val::Obj(b)) => {392 let fields = a.visible_fields();393 if fields != b.visible_fields() {394 return Ok(false);395 }396 for field in fields {397 if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {398 return Ok(false);399 }400 }401 Ok(true)402 }403 (a, b) => Ok(primitive_equals(&a, &b)?),404 }405}