git.delta.rocks / jrsonnet / refs/commits / 067b138a5c9b

difftreelog

chore(evaluator) partial interpreter implementation

Лач2020-05-29parent: #45767d3.patch.diff
in: master

8 files changed

modifiedcrates/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" }
addedcrates/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,
+		})))
+	}
+}
addedcrates/jsonnet-evaluator/src/ctx.rsdiffbeforeafterboth

no content

addedcrates/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()
+			}
+		}
+	};
+}
addedcrates/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),
+	}
+}
modifiedcrates/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(),
 		);
 	}
 }
addedcrates/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)
+	}
+}
addedcrates/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)
+	}
+}