difftreelog
chore(evaluator) partial interpreter implementation
in: master
8 files changed
crates/jsonnet-evaluator/Cargo.tomldiffbeforeafterboth--- a/crates/jsonnet-evaluator/Cargo.toml
+++ b/crates/jsonnet-evaluator/Cargo.toml
@@ -8,3 +8,6 @@
[dependencies]
jsonnet-parser = { path = "../jsonnet-parser" }
+
+[dev-dependencies]
+jsonnet-stdlib = { version = "0.1.0", path = "../jsonnet-stdlib" }
crates/jsonnet-evaluator/src/binding.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jsonnet-evaluator/src/binding.rs
@@ -0,0 +1,43 @@
+use crate::{
+ ArgsBindingLazyVal, BoxedContextCreator, BoxedLazyVal, NoArgsBindingLazyVal, ObjValue, Val,
+};
+use jsonnet_parser::{Expr, ParamsDesc};
+use std::{fmt::Debug, rc::Rc};
+
+pub trait Binding: Debug {
+ fn evaluate(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Val;
+}
+pub type BoxedBinding = Rc<dyn Binding>;
+
+#[derive(Debug)]
+pub struct NoArgsBinding {
+ pub expr: Expr,
+ pub context_creator: BoxedContextCreator,
+}
+impl Binding for NoArgsBinding {
+ fn evaluate(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Val {
+ Val::Lazy(BoxedLazyVal(Rc::new(NoArgsBindingLazyVal {
+ context_creator: self.context_creator.clone(),
+ expr: self.expr.clone(),
+ this,
+ super_obj,
+ })))
+ }
+}
+#[derive(Debug)]
+pub struct ArgsBinding {
+ pub expr: Expr,
+ pub args: ParamsDesc,
+ pub context_creator: BoxedContextCreator,
+}
+impl Binding for ArgsBinding {
+ fn evaluate(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Val {
+ Val::Lazy(BoxedLazyVal(Rc::new(ArgsBindingLazyVal {
+ context_creator: self.context_creator.clone(),
+ expr: self.expr.clone(),
+ args: self.args.clone(),
+ this,
+ super_obj,
+ })))
+ }
+}
crates/jsonnet-evaluator/src/ctx.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jsonnet-evaluator/src/ctx.rs
@@ -0,0 +1,110 @@
+use crate::{dummy_debug, future_wrapper, BoxedBinding, ObjValue};
+use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
+
+pub trait ContextCreator: Debug {
+ fn create_context(&self, this: &Option<ObjValue>, super_obj: &Option<ObjValue>) -> Context;
+}
+pub type BoxedContextCreator = Rc<dyn ContextCreator>;
+
+#[derive(Debug)]
+pub struct ConstantContextCreator {
+ pub context: FutureContext,
+}
+impl ContextCreator for ConstantContextCreator {
+ fn create_context(&self, _this: &Option<ObjValue>, _super_obj: &Option<ObjValue>) -> Context {
+ self.context.clone().unwrap()
+ }
+}
+
+future_wrapper!(Context, FutureContext);
+
+#[derive(Debug)]
+struct ContextInternals {
+ dollar: Option<ObjValue>,
+ this: Option<ObjValue>,
+ super_obj: Option<ObjValue>,
+ bindings: Rc<RefCell<HashMap<String, BoxedBinding>>>,
+}
+pub struct Context(Rc<ContextInternals>);
+dummy_debug!(Context);
+impl Context {
+ pub fn new_future() -> FutureContext {
+ FutureContext(Rc::new(RefCell::new(None)))
+ }
+
+ pub fn dollar(&self) -> &Option<ObjValue> {
+ &self.0.dollar
+ }
+
+ pub fn this(&self) -> &Option<ObjValue> {
+ &self.0.this
+ }
+
+ pub fn super_obj(&self) -> &Option<ObjValue> {
+ &self.0.super_obj
+ }
+
+ pub fn new() -> Context {
+ Context(Rc::new(ContextInternals {
+ dollar: None,
+ this: None,
+ super_obj: None,
+ bindings: Rc::new(RefCell::new(HashMap::new())),
+ }))
+ }
+
+ pub fn binding(&self, name: &str) -> BoxedBinding {
+ self.0
+ .bindings
+ .borrow()
+ .get(name)
+ .map(|e| e.clone())
+ .unwrap_or_else(|| {
+ panic!("can't find {} in {:?}", name, self);
+ })
+ }
+ pub fn into_future(self, ctx: FutureContext) -> Context {
+ {
+ ctx.0.borrow_mut().replace(self);
+ }
+ ctx.unwrap()
+ }
+
+ pub fn extend(
+ &self,
+ new_bindings: HashMap<String, BoxedBinding>,
+ new_dollar: Option<ObjValue>,
+ new_this: Option<ObjValue>,
+ new_super_obj: Option<ObjValue>,
+ ) -> Context {
+ let dollar = new_dollar.or(self.0.dollar.clone());
+ let this = new_this.or(self.0.this.clone());
+ let super_obj = new_super_obj.or(self.0.super_obj.clone());
+ let bindings = if new_bindings.is_empty() {
+ self.0.bindings.clone()
+ } else {
+ let new = self.0.bindings.clone();
+ for (k, v) in new_bindings.into_iter() {
+ new.borrow_mut().insert(k, v);
+ }
+ new
+ };
+ Context(Rc::new(ContextInternals {
+ dollar,
+ this,
+ super_obj,
+ bindings,
+ }))
+ }
+}
+impl PartialEq for Context {
+ fn eq(&self, other: &Self) -> bool {
+ Rc::ptr_eq(&self.0, &other.0)
+ }
+}
+
+impl Clone for Context {
+ fn clone(&self) -> Self {
+ Context(self.0.clone())
+ }
+}
crates/jsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jsonnet-evaluator/src/dynamic.rs
@@ -0,0 +1,55 @@
+#[macro_export]
+macro_rules! dynamic_wrapper {
+ ($orig: ident, $wrapper: ident) => {
+ #[derive(Debug, Clone)]
+ pub struct $wrapper(pub std::rc::Rc<dyn $orig>);
+ impl std::ops::Deref for $wrapper {
+ type Target = dyn $orig;
+ fn deref(&self) -> &Self::Target {
+ &*self.0
+ }
+ }
+ impl std::cmp::PartialEq for $wrapper {
+ fn eq(&self, other: &Self) -> bool {
+ Rc::ptr_eq(&self.0, &other.0)
+ }
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! future_wrapper {
+ ($orig: ty, $wrapper: ident) => {
+ #[derive(Debug, Clone)]
+ pub struct $wrapper(pub std::rc::Rc<std::cell::RefCell<Option<$orig>>>);
+ impl $wrapper {
+ pub fn unwrap(self) -> $orig {
+ self.0.borrow().as_ref().map(|e| e.clone()).unwrap()
+ }
+ pub fn new() -> Self {
+ $wrapper(std::rc::Rc::new(std::cell::RefCell::new(None)))
+ }
+ pub fn fill(self, val: $orig) -> $orig {
+ if self.0.borrow().is_some() {
+ panic!("wrapper is filled already");
+ }
+ {
+ self.0.borrow_mut().replace(val);
+ }
+ self.unwrap()
+ }
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! dummy_debug {
+ ($struct: ident) => {
+ impl std::fmt::Debug for $struct {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct(std::stringify!($struct))
+ .finish_non_exhaustive()
+ }
+ }
+ };
+}
crates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterbothno content
crates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/lib.rs
+++ b/crates/jsonnet-evaluator/src/lib.rs
@@ -1,59 +1,187 @@
#![feature(box_syntax, box_patterns)]
+#![feature(type_alias_impl_trait)]
+#![feature(debug_non_exhaustive)]
+mod binding;
+mod ctx;
+mod dynamic;
+mod evaluate;
+mod obj;
+mod val;
+
+pub use binding::*;
+pub use ctx::*;
+pub use dynamic::*;
+pub use evaluate::*;
use jsonnet_parser::*;
+pub use obj::*;
+use std::fmt::Debug;
+use std::rc::Rc;
+pub use val::*;
-#[derive(Debug, Clone, PartialEq)]
-pub enum Val {
- Str(String),
- Num(f64),
+pub trait FunctionRhs: Debug {
+ fn evaluate(&self, ctx: Context) -> Val;
}
+dynamic_wrapper!(FunctionRhs, BoxedFunctionRhs);
-pub fn evaluate(expr: &Expr) -> Val {
- use Expr::*;
- match expr {
- Parened(e) => evaluate(e),
- Str(v) => Val::Str(v.clone()),
- Num(v) => Val::Num(*v),
- BinaryOp(v1, o, v2) => match (evaluate(v1), o, evaluate(v2)) {
- (Val::Str(v1), BinaryOpType::Add, Val::Str(v2)) => Val::Str(v1 + &v2),
- (Val::Str(v1), BinaryOpType::Mul, Val::Num(v2)) => {
- Val::Str(v1.repeat(v2 as usize))
- },
- (Val::Num(v1), BinaryOpType::Add, Val::Num(v2)) => Val::Num(v1 + v2),
- (Val::Num(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Num(v1 * v2),
- _ => panic!("Can't evaluate binary op: {:?} {:?} {:?}", v1, o, v2),
- },
- _ => panic!("Can't evaluate: {:?}", expr),
- }
+pub trait FunctionDefault: Debug {
+ fn default(&self, ctx: Context, expr: Expr) -> Val;
}
+dynamic_wrapper!(FunctionDefault, BoxedFunctionDefault);
#[cfg(test)]
pub mod tests {
- use super::{evaluate, Val};
- use jsonnet_parser::parse;
+ use super::{evaluate, Context, Val};
+ use jsonnet_parser::*;
+
+ // macro_rules! eval {
+ // ($str: expr) => {
+ // evaluate(Context::new(), &parse($str).unwrap())
+ // };
+ // }
+ macro_rules! assert_eval {
+ ($str: expr) => {
+ assert_eq!(
+ evaluate(Context::new(), &parse($str).unwrap()),
+ Val::Literal(LiteralType::True)
+ )
+ };
+ }
+ macro_rules! assert_json {
+ ($str: expr, $out: expr) => {
+ assert_eq!(
+ format!("{}", evaluate(Context::new(), &parse($str).unwrap())),
+ $out
+ )
+ };
+ }
+ macro_rules! assert_eval_neg {
+ ($str: expr) => {
+ assert_eq!(
+ evaluate(Context::new(), &parse($str).unwrap()),
+ Val::Literal(LiteralType::False)
+ )
+ };
+ }
+
+ /// Sanity checking, before trusting to another tests
+ #[test]
+ fn equality_operator() {
+ assert_eval!("2 == 2");
+ assert_eval_neg!("2 != 2");
+ assert_eval!("2 != 3");
+ assert_eval_neg!("2 == 3");
+ assert_eval!("'Hello' == 'Hello'");
+ assert_eval_neg!("'Hello' != 'Hello'");
+ assert_eval!("'Hello' != 'World'");
+ assert_eval_neg!("'Hello' == 'World'");
+ }
+
#[test]
fn math_evaluation() {
- assert_eq!(evaluate(&parse("2+2*2").unwrap()), Val::Num(6.0));
+ assert_eval!("2 + 2 * 2 == 6");
+ assert_eval!("3 + (2 + 2 * 2) == 9");
+ }
+
+ #[test]
+ fn string_concat() {
+ assert_eval!("'Hello' + 'World' == 'HelloWorld'");
+ assert_eval!("'Hello' * 3 == 'HelloHelloHello'");
+ assert_eval!("'Hello' + 'World' * 3 == 'HelloWorldWorldWorld'");
+ }
+
+ #[test]
+ fn local() {
+ assert_eval!("local a = 2; local b = 3; a + b == 5");
+ assert_eval!("local a = 1, b = a + 1; a + b == 3");
+ assert_eval!("local a = 1; local a = 2; a == 2");
+ }
+
+ #[test]
+ fn object_lazyness() {
+ assert_json!("local a = {a:error 'test'}; {}", r#"{}"#);
+ }
+
+ #[test]
+ fn object_inheritance() {
+ assert_json!("{a:self.b} + {b:3}", r#"{"a":3,"b":3}"#);
+ }
+
+ #[test]
+ fn test_object() {
+ assert_json!("{a:2}", r#"{"a":2}"#);
+ assert_json!("{a:2+2}", r#"{"a":4}"#);
+ assert_json!("{a:2}+{b:2}", r#"{"a":2,"b":2}"#);
+ assert_json!("{b:3}+{b:2}", r#"{"b":2}"#);
+ assert_json!("{b:3}+{b+:2}", r#"{"b":5}"#);
+ assert_json!("local test='a'; {[test]:2}", r#"{"a":2}"#);
+ assert_json!(
+ r#"
+ {
+ name: "Alice",
+ welcome: "Hello " + self.name + "!",
+ }
+ "#,
+ r#"{"name":"Alice","welcome":"Hello Alice!"}"#
+ );
+ assert_json!(
+ r#"
+ {
+ name: "Alice",
+ welcome: "Hello " + self.name + "!",
+ } + {
+ name: "Bob"
+ }
+ "#,
+ r#"{"name":"Bob","welcome":"Hello Bob!"}"#
+ );
}
#[test]
- fn math_evaluation_with_parened() {
- assert_eq!(evaluate(&parse("3+(2+2*2)").unwrap()), Val::Num(9.0));
+ fn functions() {
+ assert_json!(r#"local a = function(b, c = 2) b + c; a(2)"#, "4");
+ assert_json!(
+ r#"local a = function(b, c = "Dear") b + c + d, d = "World"; a("Hello")"#,
+ r#""HelloDearWorld""#
+ );
}
#[test]
- fn string_concat() {
- assert_eq!(
- evaluate(&parse("\"Hello\"+\"World\"").unwrap()),
- Val::Str("HelloWorld".to_owned()),
+ fn local_methods() {
+ assert_json!(r#"local a(b, c = 2) = b + c; a(2)"#, "4");
+ assert_json!(
+ r#"local a(b, c = "Dear") = b + c + d, d = "World"; a("Hello")"#,
+ r#""HelloDearWorld""#
);
}
#[test]
- fn string_repeat() {
- assert_eq!(
- evaluate(&parse("\"Hello\"*3").unwrap()),
- Val::Str("HelloHelloHello".to_owned()),
+ fn object_locals() {
+ assert_json!(r#"{local a = 3, b: a}"#, r#"{"b":3}"#);
+ assert_json!(r#"{local a = 3, local c = a, b: c}"#, r#"{"b":3}"#);
+ assert_json!(
+ r#"{local a = function (b) {[b]:4}, test: a("test")}"#,
+ r#"{"test":{"test":4}}"#
+ );
+ }
+
+ // We can't trust other tests (And official jsonnet testsuite), if assert is not working correctly
+ #[test]
+ fn std_assert_ok() {
+ let std = "local std = ".to_owned() + jsonnet_stdlib::STDLIB_STR + ";";
+ evaluate(
+ Context::new(),
+ &parse(&(std + "std.assertEqual(4.5 << 2, 16,)")).unwrap(),
+ );
+ }
+
+ #[test]
+ #[should_panic]
+ fn std_assert_failure() {
+ let std = "local std = ".to_owned() + jsonnet_stdlib::STDLIB_STR + ";";
+ evaluate(
+ Context::new(),
+ &parse(&(std + "std.assertEqual(4.5 << 2, 15,)")).unwrap(),
);
}
}
crates/jsonnet-evaluator/src/obj.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jsonnet-evaluator/src/obj.rs
@@ -0,0 +1,83 @@
+use crate::{dummy_debug, evaluate_binary_op, BoxedBinding, Val};
+use jsonnet_parser::{BinaryOpType, Visibility};
+use std::{
+ collections::{BTreeMap, BTreeSet},
+ rc::Rc,
+};
+
+#[derive(Debug)]
+pub struct ObjMember {
+ pub add: bool,
+ pub visibility: Visibility,
+ pub invoke: BoxedBinding,
+}
+
+#[derive(Debug)]
+pub struct ObjValueInternals {
+ super_obj: Option<ObjValue>,
+ this_entries: Rc<BTreeMap<String, ObjMember>>,
+}
+pub struct ObjValue(Rc<ObjValueInternals>);
+dummy_debug!(ObjValue);
+impl ObjValue {
+ pub fn new(
+ super_obj: Option<ObjValue>,
+ this_entries: Rc<BTreeMap<String, ObjMember>>,
+ ) -> ObjValue {
+ ObjValue(Rc::new(ObjValueInternals {
+ super_obj,
+ this_entries,
+ }))
+ }
+ pub fn with_super(&self, super_obj: ObjValue) -> ObjValue {
+ match &self.0.super_obj {
+ None => ObjValue::new(Some(super_obj), self.0.this_entries.clone()),
+ Some(v) => ObjValue::new(Some(v.with_super(super_obj)), self.0.this_entries.clone()),
+ }
+ }
+ pub fn fields(&self) -> BTreeSet<String> {
+ let mut fields = BTreeSet::new();
+ self.0.this_entries.keys().for_each(|k| {
+ fields.insert(k.clone());
+ });
+ if self.0.super_obj.is_some() {
+ for field in self.0.super_obj.clone().unwrap().fields() {
+ fields.insert(field);
+ }
+ }
+ fields
+ }
+ pub fn get_raw(&self, key: &str, real_this: Option<ObjValue>) -> Option<Val> {
+ match (self.0.this_entries.get(key), &self.0.super_obj) {
+ (Some(k), None) => Some(k.invoke.evaluate(
+ real_this.or_else(|| Some(self.clone())),
+ self.0.super_obj.clone().map(|e| e.clone()),
+ )),
+ (Some(k), Some(s)) => {
+ let our = k
+ .invoke
+ .evaluate(Some(self.clone()), self.0.super_obj.clone());
+ if k.add {
+ s.get_raw(key, Some(self.clone()))
+ .map_or(Some(our.clone()), |v| {
+ Some(evaluate_binary_op(&v, BinaryOpType::Add, &our))
+ })
+ } else {
+ Some(our)
+ }
+ }
+ (None, Some(s)) => s.get_raw(key, Some(self.clone())),
+ (None, None) => None,
+ }
+ }
+}
+impl Clone for ObjValue {
+ fn clone(&self) -> Self {
+ ObjValue(self.0.clone())
+ }
+}
+impl PartialEq for ObjValue {
+ fn eq(&self, other: &Self) -> bool {
+ Rc::ptr_eq(&self.0, &other.0)
+ }
+}
crates/jsonnet-evaluator/src/val.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jsonnet-evaluator/src/val.rs
@@ -0,0 +1,216 @@
+use crate::{
+ dynamic_wrapper, evaluate, evaluate_method, BoxedContextCreator, Context, FunctionDefault,
+ FunctionRhs, ObjValue,
+};
+use crate::{Binding, BoxedBinding, BoxedFunctionDefault, BoxedFunctionRhs, FutureContext};
+use jsonnet_parser::{ArgsDesc, Expr, LiteralType, Param, ParamsDesc};
+use std::{
+ collections::HashMap,
+ fmt::{Debug, Display},
+ ops::Deref,
+ rc::Rc,
+};
+
+pub trait LazyVal: Debug {
+ fn evaluate(&self) -> Val;
+}
+dynamic_wrapper!(LazyVal, BoxedLazyVal);
+
+#[derive(Debug)]
+pub struct PlainLazyVal {
+ pub expr: Expr,
+ pub context: Context,
+}
+impl LazyVal for PlainLazyVal {
+ fn evaluate(&self) -> Val {
+ evaluate(self.context.clone(), &self.expr)
+ }
+}
+
+#[derive(Debug)]
+pub struct NoArgsBindingLazyVal {
+ pub expr: Expr,
+ pub context_creator: BoxedContextCreator,
+
+ pub this: Option<ObjValue>,
+ pub super_obj: Option<ObjValue>,
+}
+impl LazyVal for NoArgsBindingLazyVal {
+ fn evaluate(&self) -> Val {
+ evaluate(
+ self.context_creator
+ .create_context(&self.this, &self.super_obj),
+ &self.expr,
+ )
+ }
+}
+
+#[derive(Debug)]
+pub struct ArgsBindingLazyVal {
+ pub expr: Expr,
+ pub args: ParamsDesc,
+ pub context_creator: BoxedContextCreator,
+
+ pub this: Option<ObjValue>,
+ pub super_obj: Option<ObjValue>,
+}
+impl LazyVal for ArgsBindingLazyVal {
+ fn evaluate(&self) -> Val {
+ evaluate_method(
+ self.context_creator
+ .create_context(&self.this, &self.super_obj),
+ &self.expr,
+ self.args.clone(),
+ )
+ }
+}
+
+#[derive(Debug)]
+pub struct FunctionDefaultBinding {
+ eval: BoxedFunctionDefault,
+ default: Expr,
+ ctx: FutureContext,
+}
+impl Binding for FunctionDefaultBinding {
+ fn evaluate(&self, _this: Option<ObjValue>, _super_obj: Option<ObjValue>) -> Val {
+ self.eval
+ .default(self.ctx.clone().unwrap(), self.default.clone())
+ }
+}
+
+#[derive(Debug)]
+pub struct ValBinding {
+ val: Val,
+}
+impl Binding for ValBinding {
+ fn evaluate(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Val {
+ self.val.clone()
+ }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct FuncDesc {
+ pub ctx: Context,
+ pub params: ParamsDesc,
+ pub eval_rhs: BoxedFunctionRhs,
+ pub eval_default: BoxedFunctionDefault,
+}
+impl FuncDesc {
+ // TODO: Check for unset variables
+ pub fn evaluate(&self, args: Vec<(Option<String>, Val)>) -> Val {
+ let mut new_bindings: HashMap<String, BoxedBinding> = HashMap::new();
+ let future_ctx = Context::new_future();
+
+ self.params
+ .with_defaults()
+ .into_iter()
+ .for_each(|Param(name, default)| {
+ new_bindings.insert(
+ name,
+ Rc::new(FunctionDefaultBinding {
+ eval: self.eval_default.clone(),
+ default: *default.unwrap().clone(),
+ ctx: future_ctx.clone(),
+ }),
+ );
+ });
+ for (name, val) in args.iter().filter(|e| e.0.is_some()) {
+ new_bindings.insert(
+ name.as_ref().unwrap().clone(),
+ Rc::new(ValBinding { 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(), Rc::new(ValBinding { val: val.clone() }));
+ }
+ }
+ let ctx = self
+ .ctx
+ .extend(new_bindings, None, None, None)
+ .into_future(future_ctx);
+ self.eval_rhs.evaluate(ctx)
+ }
+}
+
+#[derive(Debug, PartialEq, Clone)]
+pub enum Val {
+ Literal(LiteralType),
+ Str(String),
+ Num(f64),
+ Lazy(BoxedLazyVal),
+ Arr(Vec<Val>),
+ Obj(ObjValue),
+ Func(FuncDesc),
+}
+impl Val {
+ pub fn unwrap_if_lazy(self) -> Self {
+ if let Val::Lazy(v) = self {
+ v.evaluate().unwrap_if_lazy()
+ } else {
+ self
+ }
+ }
+ pub fn type_of(&self) -> &'static str {
+ match self {
+ Val::Str(..) => "string",
+ Val::Num(..) => "number",
+ Val::Arr(..) => "array",
+ Val::Obj(..) => "object",
+ Val::Func(..) => "function",
+ _ => panic!("no native type found"),
+ }
+ }
+}
+impl Display for Val {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Val::Literal(v) => write!(f, "{}", v)?,
+ Val::Str(str) => write!(f, "\"{}\"", str)?,
+ Val::Num(n) => write!(f, "{}", n)?,
+ Val::Arr(values) => {
+ write!(f, "[")?;
+ let mut first = true;
+ for value in values {
+ if first {
+ first = false;
+ } else {
+ write!(f, ",")?;
+ }
+ write!(f, "{}", value)?;
+ }
+ write!(f, "]")?;
+ }
+ Val::Obj(value) => {
+ write!(f, "{{")?;
+ let mut first = true;
+ for field in value.fields() {
+ if first {
+ first = false;
+ } else {
+ write!(f, ",")?;
+ }
+ write!(f, "\"{}\":", field)?;
+ write!(f, "{}", value.get_raw(&field, None).unwrap())?;
+ }
+ write!(f, "}}")?;
+ }
+ Val::Lazy(lazy) => {
+ write!(f, "{}", lazy.evaluate())?;
+ }
+ Val::Func(_) => {
+ write!(f, "<<FUNC>>")?;
+ }
+ v => panic!("no json equivalent for {:?}", v),
+ };
+ Ok(())
+ }
+}
+
+pub fn bool_val(v: bool) -> Val {
+ if v {
+ Val::Literal(LiteralType::True)
+ } else {
+ Val::Literal(LiteralType::False)
+ }
+}