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.rsdiffbeforeafterboth1use crate::BoxedLazyVal;2use crate::{3 bool_val, ArgsBinding, BoxedBinding, BoxedContextCreator, ConstantContextCreator, Context,4 FuncDesc, FunctionDefault, FunctionRhs, NoArgsBinding, Val,5};6use crate::{7 future_wrapper, BoxedFunctionDefault, BoxedFunctionRhs, ContextCreator, ObjMember, ObjValue,8 PlainLazyVal,9};10use jsonnet_parser::{11 ArgsDesc, BinaryOpType, BindSpec, Expr, FieldMember, LiteralType, Member, ObjBody, ParamsDesc,12 Visibility,13};14use std::{15 cell::RefCell,16 collections::{BTreeMap, HashMap},17 rc::Rc,18};1920pub fn evaluate_binding<'t>(21 b: &BindSpec,22 context_creator: BoxedContextCreator,23) -> (String, BoxedBinding) {24 if let Some(args) = &b.params {25 (26 b.name.clone(),27 Rc::new(ArgsBinding {28 expr: *b.value.clone(),29 args: args.clone(),30 context_creator: context_creator.clone(),31 }),32 )33 } else {34 (35 b.name.clone(),36 Rc::new(NoArgsBinding {37 expr: *b.value.clone(),38 context_creator: context_creator.clone(),39 }) as BoxedBinding,40 )41 }42}4344#[derive(Debug)]45struct MethodRhs {46 rhs: Expr,47}48impl FunctionRhs for MethodRhs {49 fn evaluate(&self, ctx: Context) -> Val {50 evaluate(ctx, &self.rhs)51 }52}5354#[derive(Debug)]55struct MethodDefault {}56impl FunctionDefault for MethodDefault {57 fn default(&self, ctx: Context, expr: Expr) -> Val {58 evaluate(ctx, &expr)59 }60}6162pub fn evaluate_method(ctx: Context, expr: &Expr, arg_spec: ParamsDesc) -> Val {63 Val::Func(FuncDesc {64 ctx,65 params: arg_spec,66 eval_rhs: BoxedFunctionRhs(Rc::new(MethodRhs { rhs: expr.clone() })),67 eval_default: BoxedFunctionDefault(Rc::new(MethodDefault {})),68 })69}7071pub fn evaluate_field_name(context: Context, field_name: &jsonnet_parser::FieldName) -> String {72 match field_name {73 jsonnet_parser::FieldName::Fixed(n) => n.clone(),74 jsonnet_parser::FieldName::Dyn(expr) => {75 let name = evaluate(context, expr).unwrap_if_lazy();76 match name {77 Val::Str(n) => n.clone(),78 _ => panic!(79 "dynamic field name can be only evaluated to 'string', got: {:?}",80 name81 ),82 }83 }84 }85}8687pub fn evaluate_binary_op(a: &Val, op: BinaryOpType, b: &Val) -> Val {88 match (a, op, b) {89 (Val::Lazy(l), o, r) => evaluate_binary_op(&l.evaluate(), o, r),90 (l, o, Val::Lazy(r)) => evaluate_binary_op(l, o, &r.evaluate()),9192 (Val::Str(v1), BinaryOpType::Add, Val::Str(v2)) => Val::Str(v1.to_owned() + &v2),93 (Val::Str(v1), BinaryOpType::Eq, Val::Str(v2)) => bool_val(v1 == v2),94 (Val::Str(v1), BinaryOpType::Ne, Val::Str(v2)) => bool_val(v1 != v2),9596 (Val::Str(v1), BinaryOpType::Add, Val::Num(v2)) => Val::Str(format!("{}{}", v1, v2)),97 (Val::Str(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Str(v1.repeat(*v2 as usize)),9899 (Val::Obj(v1), BinaryOpType::Add, Val::Obj(v2)) => Val::Obj(v2.with_super(v1.clone())),100101 (Val::Arr(a), BinaryOpType::Add, Val::Arr(b)) => Val::Arr([&a[..], &b[..]].concat()),102103 (Val::Num(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Num(v1 * v2),104 (Val::Num(v1), BinaryOpType::Div, Val::Num(v2)) => Val::Num(v1 / v2),105 (Val::Num(v1), BinaryOpType::Mod, Val::Num(v2)) => Val::Num(v1 % v2),106107 (Val::Num(v1), BinaryOpType::Add, Val::Num(v2)) => Val::Num(v1 + v2),108 (Val::Num(v1), BinaryOpType::Sub, Val::Num(v2)) => Val::Num(v1 - v2),109110 (Val::Num(v1), BinaryOpType::Lhs, Val::Num(v2)) => {111 Val::Num(((*v1 as i32) << (*v2 as i32)) as f64)112 }113 (Val::Num(v1), BinaryOpType::Rhs, Val::Num(v2)) => {114 Val::Num(((*v1 as i32) >> (*v2 as i32)) as f64)115 }116117 (Val::Num(v1), BinaryOpType::Lt, Val::Num(v2)) => bool_val(v1 < v2),118 (Val::Num(v1), BinaryOpType::Gt, Val::Num(v2)) => bool_val(v1 > v2),119 (Val::Num(v1), BinaryOpType::Lte, Val::Num(v2)) => bool_val(v1 <= v2),120 (Val::Num(v1), BinaryOpType::Gte, Val::Num(v2)) => bool_val(v1 >= v2),121122 (Val::Num(v1), BinaryOpType::Eq, Val::Num(v2)) => bool_val(v1 == v2),123 (Val::Num(v1), BinaryOpType::Ne, Val::Num(v2)) => bool_val(v1 != v2),124125 (Val::Num(v1), BinaryOpType::BitAnd, Val::Num(v2)) => {126 Val::Num(((*v1 as i32) & (*v2 as i32)) as f64)127 }128 (Val::Num(v1), BinaryOpType::BitOr, Val::Num(v2)) => {129 Val::Num(((*v1 as i32) | (*v2 as i32)) as f64)130 }131 (Val::Num(v1), BinaryOpType::BitXor, Val::Num(v2)) => {132 Val::Num(((*v1 as i32) ^ (*v2 as i32)) as f64)133 }134 _ => panic!("no rules for binary operation: {:?} {:?} {:?}", a, op, b),135 }136}137138future_wrapper!(HashMap<String, BoxedBinding>, FutureNewBindings);139140#[derive(Debug)]141pub struct ObjectContextCreator {142 original: Context,143 future_bindings: FutureNewBindings,144}145146impl ContextCreator for ObjectContextCreator {147 fn create_context(&self, this: &Option<ObjValue>, super_obj: &Option<ObjValue>) -> Context {148 self.original.extend(149 self.future_bindings.clone().unwrap(),150 self.original.dollar().clone().or_else(|| this.clone()),151 this.clone(),152 super_obj.clone(),153 )154 }155}156157// TODO: Asserts158pub fn evaluate_object(context: Context, object: ObjBody) -> ObjValue {159 match object {160 ObjBody::MemberList(members) => {161 let future_bindings = FutureNewBindings::new();162 let binding_context_creator = Rc::new(ObjectContextCreator {163 future_bindings: future_bindings.clone(),164 original: context.clone(),165 });166 let mut bindings: HashMap<String, BoxedBinding> = HashMap::new();167 for (n, b) in members168 .iter()169 .filter_map(|m| match m {170 Member::BindStmt(b) => Some(b.clone()),171 _ => None,172 })173 .map(|b| evaluate_binding(&b, binding_context_creator.clone()))174 {175 bindings.insert(n, b);176 }177 let bindings = future_bindings.fill(bindings);178 let mut new_members = BTreeMap::new();179 for member in members.iter() {180 match member {181 Member::Field(FieldMember {182 name,183 plus,184 params: None,185 visibility,186 value,187 }) => {188 let name = evaluate_field_name(context.clone(), name);189 new_members.insert(190 name,191 ObjMember {192 add: *plus,193 visibility: visibility.clone(),194 invoke: Rc::new(NoArgsBinding {195 context_creator: binding_context_creator.clone(),196 expr: value.clone(),197 }),198 },199 );200 }201 Member::Field(FieldMember {202 name,203 params: Some(params),204 value,205 ..206 }) => {207 let name = evaluate_field_name(context.clone(), name);208 new_members.insert(209 name,210 ObjMember {211 add: false,212 visibility: Visibility::Hidden,213 invoke: Rc::new(ArgsBinding {214 expr: value.clone(),215 args: params.clone(),216 context_creator: binding_context_creator.clone(),217 }),218 },219 );220 }221 Member::BindStmt(_) => {}222 Member::AssertStmt(_) => {}223 _ => todo!(),224 }225 }226 ObjValue::new(None, Rc::new(new_members))227 }228 _ => todo!(),229 }230}231232pub fn evaluate(context: Context, expr: &Expr) -> Val {233 use Expr::*;234 match &*expr {235 Literal(t) => Val::Literal(t.clone()),236 Parened(e) => evaluate(context, e),237 Str(v) => Val::Str(v.clone()),238 Num(v) => Val::Num(*v),239 BinaryOp(v1, o, v2) => {240 evaluate_binary_op(&evaluate(context.clone(), v1), *o, &evaluate(context, v2))241 }242 Var(name) => {243 let variable = context.binding(&name);244 let val = variable.evaluate(None, None);245 val246 }247 Index(box value, box index) => {248 match (249 evaluate(context.clone(), value).unwrap_if_lazy(),250 evaluate(context.clone(), index),251 ) {252 (Val::Literal(LiteralType::Super), _idx) => todo!(),253 (Val::Literal(LiteralType::This), idx) => match &idx.unwrap_if_lazy() {254 Val::Str(str) => context255 .this()256 .clone()257 .unwrap_or_else(|| panic!("'this' is not defined in current context"))258 .get_raw(str, None)259 .unwrap_or_else(|| {260 panic!(261 "key {} not found in current context 'this' ({:?})",262 str,263 context.this()264 )265 }),266 _ => panic!("bad index"),267 },268 (Val::Obj(v), Val::Str(s)) => v269 .get_raw(&s, None)270 .unwrap_or_else(|| panic!("{} not found in {:?}", s, v)),271 (v, i) => todo!("not implemented: {:?}[{:?}]", v, i.unwrap_if_lazy()),272 }273 }274 LocalExpr(bindings, returned) => {275 let mut new_bindings: HashMap<String, BoxedBinding> = HashMap::new();276 let future_context = Context::new_future();277278 let context_creator = Rc::new(ConstantContextCreator {279 context: future_context.clone(),280 });281 for (k, v) in bindings282 .iter()283 .map(move |b| evaluate_binding(b, context_creator.clone()))284 {285 new_bindings.insert(k, v);286 }287288 let context = context289 .extend(new_bindings, None, None, None)290 .into_future(future_context);291 evaluate(context, &*returned.clone())292 }293 Obj(body) => Val::Obj(evaluate_object(context, body.clone())),294 Apply(box value, ArgsDesc(args)) => {295 let value = evaluate(context.clone(), value).unwrap_if_lazy();296 match value {297 Val::Func(f) => f.evaluate(298 args.clone()299 .into_iter()300 .map(|a| {301 (302 a.0,303 Val::Lazy(BoxedLazyVal(Rc::new(PlainLazyVal {304 context: context.clone(),305 expr: *a.1,306 }))),307 )308 })309 .collect(),310 ),311 _ => panic!("{:?} is not a function", value),312 }313 }314 Function(params, body) => evaluate_method(context, body, params.clone()),315 Error(e) => panic!("error: {}", evaluate(context, e)),316 IfElse {317 cond,318 cond_then,319 cond_else,320 } => match evaluate(context.clone(), &cond.0).unwrap_if_lazy() {321 Val::Literal(LiteralType::True) => evaluate(context.clone(), cond_then),322 Val::Literal(LiteralType::False) => match cond_else {323 Some(v) => evaluate(context.clone(), v),324 None => Val::Literal(LiteralType::False),325 },326 v => panic!("if condition evaluated to {:?} (boolean needed instead)", v),327 },328 _ => panic!("evaluation not implemented: {:?}", expr),329 }330}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)
+ }
+}