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.rsdiffbeforeafterboth1use crate::{2 ArgsBindingLazyVal, BoxedContextCreator, BoxedLazyVal, NoArgsBindingLazyVal, ObjValue, Val,3};4use jsonnet_parser::{Expr, ParamsDesc};5use std::{fmt::Debug, rc::Rc};67pub trait Binding: Debug {8 fn evaluate(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Val;9}10pub type BoxedBinding = Rc<dyn Binding>;1112#[derive(Debug)]13pub struct NoArgsBinding {14 pub expr: Expr,15 pub context_creator: BoxedContextCreator,16}17impl Binding for NoArgsBinding {18 fn evaluate(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Val {19 Val::Lazy(BoxedLazyVal(Rc::new(NoArgsBindingLazyVal {20 context_creator: self.context_creator.clone(),21 expr: self.expr.clone(),22 this,23 super_obj,24 })))25 }26}27#[derive(Debug)]28pub struct ArgsBinding {29 pub expr: Expr,30 pub args: ParamsDesc,31 pub context_creator: BoxedContextCreator,32}33impl Binding for ArgsBinding {34 fn evaluate(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Val {35 Val::Lazy(BoxedLazyVal(Rc::new(ArgsBindingLazyVal {36 context_creator: self.context_creator.clone(),37 expr: self.expr.clone(),38 args: self.args.clone(),39 this,40 super_obj,41 })))42 }43}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.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -0,0 +1,330 @@
+use crate::BoxedLazyVal;
+use crate::{
+ bool_val, ArgsBinding, BoxedBinding, BoxedContextCreator, ConstantContextCreator, Context,
+ FuncDesc, FunctionDefault, FunctionRhs, NoArgsBinding, Val,
+};
+use crate::{
+ future_wrapper, BoxedFunctionDefault, BoxedFunctionRhs, ContextCreator, ObjMember, ObjValue,
+ PlainLazyVal,
+};
+use jsonnet_parser::{
+ ArgsDesc, BinaryOpType, BindSpec, Expr, FieldMember, LiteralType, Member, ObjBody, ParamsDesc,
+ Visibility,
+};
+use std::{
+ cell::RefCell,
+ collections::{BTreeMap, HashMap},
+ rc::Rc,
+};
+
+pub fn evaluate_binding<'t>(
+ b: &BindSpec,
+ context_creator: BoxedContextCreator,
+) -> (String, BoxedBinding) {
+ if let Some(args) = &b.params {
+ (
+ b.name.clone(),
+ Rc::new(ArgsBinding {
+ expr: *b.value.clone(),
+ args: args.clone(),
+ context_creator: context_creator.clone(),
+ }),
+ )
+ } else {
+ (
+ b.name.clone(),
+ Rc::new(NoArgsBinding {
+ expr: *b.value.clone(),
+ context_creator: context_creator.clone(),
+ }) as BoxedBinding,
+ )
+ }
+}
+
+#[derive(Debug)]
+struct MethodRhs {
+ rhs: Expr,
+}
+impl FunctionRhs for MethodRhs {
+ fn evaluate(&self, ctx: Context) -> Val {
+ evaluate(ctx, &self.rhs)
+ }
+}
+
+#[derive(Debug)]
+struct MethodDefault {}
+impl FunctionDefault for MethodDefault {
+ fn default(&self, ctx: Context, expr: Expr) -> Val {
+ evaluate(ctx, &expr)
+ }
+}
+
+pub fn evaluate_method(ctx: Context, expr: &Expr, arg_spec: ParamsDesc) -> Val {
+ Val::Func(FuncDesc {
+ ctx,
+ params: arg_spec,
+ eval_rhs: BoxedFunctionRhs(Rc::new(MethodRhs { rhs: expr.clone() })),
+ eval_default: BoxedFunctionDefault(Rc::new(MethodDefault {})),
+ })
+}
+
+pub fn evaluate_field_name(context: Context, field_name: &jsonnet_parser::FieldName) -> String {
+ match field_name {
+ jsonnet_parser::FieldName::Fixed(n) => n.clone(),
+ jsonnet_parser::FieldName::Dyn(expr) => {
+ let name = evaluate(context, expr).unwrap_if_lazy();
+ match name {
+ Val::Str(n) => n.clone(),
+ _ => panic!(
+ "dynamic field name can be only evaluated to 'string', got: {:?}",
+ name
+ ),
+ }
+ }
+ }
+}
+
+pub fn evaluate_binary_op(a: &Val, op: BinaryOpType, b: &Val) -> Val {
+ match (a, op, b) {
+ (Val::Lazy(l), o, r) => evaluate_binary_op(&l.evaluate(), o, r),
+ (l, o, Val::Lazy(r)) => evaluate_binary_op(l, o, &r.evaluate()),
+
+ (Val::Str(v1), BinaryOpType::Add, Val::Str(v2)) => Val::Str(v1.to_owned() + &v2),
+ (Val::Str(v1), BinaryOpType::Eq, Val::Str(v2)) => bool_val(v1 == v2),
+ (Val::Str(v1), BinaryOpType::Ne, Val::Str(v2)) => bool_val(v1 != v2),
+
+ (Val::Str(v1), BinaryOpType::Add, Val::Num(v2)) => Val::Str(format!("{}{}", v1, v2)),
+ (Val::Str(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Str(v1.repeat(*v2 as usize)),
+
+ (Val::Obj(v1), BinaryOpType::Add, Val::Obj(v2)) => Val::Obj(v2.with_super(v1.clone())),
+
+ (Val::Arr(a), BinaryOpType::Add, Val::Arr(b)) => Val::Arr([&a[..], &b[..]].concat()),
+
+ (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::Mod, Val::Num(v2)) => Val::Num(v1 % v2),
+
+ (Val::Num(v1), BinaryOpType::Add, Val::Num(v2)) => Val::Num(v1 + v2),
+ (Val::Num(v1), BinaryOpType::Sub, Val::Num(v2)) => Val::Num(v1 - v2),
+
+ (Val::Num(v1), BinaryOpType::Lhs, Val::Num(v2)) => {
+ Val::Num(((*v1 as i32) << (*v2 as i32)) as f64)
+ }
+ (Val::Num(v1), BinaryOpType::Rhs, Val::Num(v2)) => {
+ Val::Num(((*v1 as i32) >> (*v2 as i32)) as f64)
+ }
+
+ (Val::Num(v1), BinaryOpType::Lt, Val::Num(v2)) => bool_val(v1 < v2),
+ (Val::Num(v1), BinaryOpType::Gt, Val::Num(v2)) => bool_val(v1 > v2),
+ (Val::Num(v1), BinaryOpType::Lte, Val::Num(v2)) => bool_val(v1 <= v2),
+ (Val::Num(v1), BinaryOpType::Gte, Val::Num(v2)) => bool_val(v1 >= v2),
+
+ (Val::Num(v1), BinaryOpType::Eq, Val::Num(v2)) => bool_val(v1 == v2),
+ (Val::Num(v1), BinaryOpType::Ne, Val::Num(v2)) => bool_val(v1 != v2),
+
+ (Val::Num(v1), BinaryOpType::BitAnd, Val::Num(v2)) => {
+ Val::Num(((*v1 as i32) & (*v2 as i32)) as f64)
+ }
+ (Val::Num(v1), BinaryOpType::BitOr, Val::Num(v2)) => {
+ Val::Num(((*v1 as i32) | (*v2 as i32)) as f64)
+ }
+ (Val::Num(v1), BinaryOpType::BitXor, Val::Num(v2)) => {
+ Val::Num(((*v1 as i32) ^ (*v2 as i32)) as f64)
+ }
+ _ => panic!("no rules for binary operation: {:?} {:?} {:?}", a, op, b),
+ }
+}
+
+future_wrapper!(HashMap<String, BoxedBinding>, FutureNewBindings);
+
+#[derive(Debug)]
+pub struct ObjectContextCreator {
+ original: Context,
+ future_bindings: FutureNewBindings,
+}
+
+impl ContextCreator for ObjectContextCreator {
+ fn create_context(&self, this: &Option<ObjValue>, super_obj: &Option<ObjValue>) -> Context {
+ self.original.extend(
+ self.future_bindings.clone().unwrap(),
+ self.original.dollar().clone().or_else(|| this.clone()),
+ this.clone(),
+ super_obj.clone(),
+ )
+ }
+}
+
+// TODO: Asserts
+pub fn evaluate_object(context: Context, object: ObjBody) -> ObjValue {
+ match object {
+ ObjBody::MemberList(members) => {
+ let future_bindings = FutureNewBindings::new();
+ let binding_context_creator = Rc::new(ObjectContextCreator {
+ future_bindings: future_bindings.clone(),
+ original: context.clone(),
+ });
+ let mut bindings: HashMap<String, BoxedBinding> = HashMap::new();
+ for (n, b) in members
+ .iter()
+ .filter_map(|m| match m {
+ Member::BindStmt(b) => Some(b.clone()),
+ _ => None,
+ })
+ .map(|b| evaluate_binding(&b, binding_context_creator.clone()))
+ {
+ bindings.insert(n, b);
+ }
+ let bindings = future_bindings.fill(bindings);
+ let mut new_members = BTreeMap::new();
+ for member in members.iter() {
+ match member {
+ Member::Field(FieldMember {
+ name,
+ plus,
+ params: None,
+ visibility,
+ value,
+ }) => {
+ let name = evaluate_field_name(context.clone(), name);
+ new_members.insert(
+ name,
+ ObjMember {
+ add: *plus,
+ visibility: visibility.clone(),
+ invoke: Rc::new(NoArgsBinding {
+ context_creator: binding_context_creator.clone(),
+ expr: value.clone(),
+ }),
+ },
+ );
+ }
+ Member::Field(FieldMember {
+ name,
+ params: Some(params),
+ value,
+ ..
+ }) => {
+ let name = evaluate_field_name(context.clone(), name);
+ new_members.insert(
+ name,
+ ObjMember {
+ add: false,
+ visibility: Visibility::Hidden,
+ invoke: Rc::new(ArgsBinding {
+ expr: value.clone(),
+ args: params.clone(),
+ context_creator: binding_context_creator.clone(),
+ }),
+ },
+ );
+ }
+ Member::BindStmt(_) => {}
+ Member::AssertStmt(_) => {}
+ _ => todo!(),
+ }
+ }
+ ObjValue::new(None, Rc::new(new_members))
+ }
+ _ => todo!(),
+ }
+}
+
+pub fn evaluate(context: Context, expr: &Expr) -> Val {
+ use Expr::*;
+ match &*expr {
+ Literal(t) => Val::Literal(t.clone()),
+ Parened(e) => evaluate(context, e),
+ Str(v) => Val::Str(v.clone()),
+ Num(v) => Val::Num(*v),
+ BinaryOp(v1, o, v2) => {
+ evaluate_binary_op(&evaluate(context.clone(), v1), *o, &evaluate(context, v2))
+ }
+ Var(name) => {
+ let variable = context.binding(&name);
+ let val = variable.evaluate(None, None);
+ val
+ }
+ Index(box value, box index) => {
+ match (
+ evaluate(context.clone(), value).unwrap_if_lazy(),
+ evaluate(context.clone(), index),
+ ) {
+ (Val::Literal(LiteralType::Super), _idx) => todo!(),
+ (Val::Literal(LiteralType::This), idx) => match &idx.unwrap_if_lazy() {
+ Val::Str(str) => context
+ .this()
+ .clone()
+ .unwrap_or_else(|| panic!("'this' is not defined in current context"))
+ .get_raw(str, None)
+ .unwrap_or_else(|| {
+ panic!(
+ "key {} not found in current context 'this' ({:?})",
+ str,
+ context.this()
+ )
+ }),
+ _ => panic!("bad index"),
+ },
+ (Val::Obj(v), Val::Str(s)) => v
+ .get_raw(&s, None)
+ .unwrap_or_else(|| panic!("{} not found in {:?}", s, v)),
+ (v, i) => todo!("not implemented: {:?}[{:?}]", v, i.unwrap_if_lazy()),
+ }
+ }
+ LocalExpr(bindings, returned) => {
+ let mut new_bindings: HashMap<String, BoxedBinding> = HashMap::new();
+ let future_context = Context::new_future();
+
+ let context_creator = Rc::new(ConstantContextCreator {
+ context: future_context.clone(),
+ });
+ for (k, v) in bindings
+ .iter()
+ .map(move |b| evaluate_binding(b, context_creator.clone()))
+ {
+ new_bindings.insert(k, v);
+ }
+
+ let context = context
+ .extend(new_bindings, None, None, None)
+ .into_future(future_context);
+ evaluate(context, &*returned.clone())
+ }
+ Obj(body) => Val::Obj(evaluate_object(context, body.clone())),
+ Apply(box value, ArgsDesc(args)) => {
+ let value = evaluate(context.clone(), value).unwrap_if_lazy();
+ match value {
+ Val::Func(f) => f.evaluate(
+ args.clone()
+ .into_iter()
+ .map(|a| {
+ (
+ a.0,
+ Val::Lazy(BoxedLazyVal(Rc::new(PlainLazyVal {
+ context: context.clone(),
+ expr: *a.1,
+ }))),
+ )
+ })
+ .collect(),
+ ),
+ _ => panic!("{:?} is not a function", value),
+ }
+ }
+ Function(params, body) => evaluate_method(context, body, params.clone()),
+ Error(e) => panic!("error: {}", evaluate(context, e)),
+ IfElse {
+ cond,
+ cond_then,
+ cond_else,
+ } => match evaluate(context.clone(), &cond.0).unwrap_if_lazy() {
+ Val::Literal(LiteralType::True) => evaluate(context.clone(), cond_then),
+ Val::Literal(LiteralType::False) => match cond_else {
+ Some(v) => evaluate(context.clone(), v),
+ None => Val::Literal(LiteralType::False),
+ },
+ v => panic!("if condition evaluated to {:?} (boolean needed instead)", v),
+ },
+ _ => panic!("evaluation not implemented: {:?}", expr),
+ }
+}
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)
+ }
+}