From 26c294dc66fa85c4864d6e1ac6acb356597a4fec Mon Sep 17 00:00:00 2001 From: Лач Date: Sun, 31 May 2020 16:00:02 +0000 Subject: [PATCH] fix(evaluator): instristics --- --- /dev/null +++ b/crates/jsonnet-evaluator/README.md @@ -0,0 +1,11 @@ +# jsonnet-evaluator + +Interpreter for parsed jsonnet tree + +## Intristics + +Some functions from stdlib are implemented as intristics + +### Intristic handling + +If indexed jsonnet object has field '__intristic_namespace__' of type 'string', then any not found field/method is resolved as `Val::Intristic(__intristic_namespace__, name)` --- a/crates/jsonnet-evaluator/src/ctx.rs +++ b/crates/jsonnet-evaluator/src/ctx.rs @@ -1,4 +1,4 @@ -use crate::{future_wrapper, rc_fn_helper, Binding, ObjValue}; +use crate::{future_wrapper, rc_fn_helper, LazyBinding, ObjValue, LazyVal, Val}; use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; rc_fn_helper!( @@ -14,7 +14,7 @@ dollar: Option, this: Option, super_obj: Option, - bindings: Rc>>, + bindings: Rc>, } pub struct Context(Rc); impl Debug for Context { @@ -46,14 +46,13 @@ dollar: None, this: None, super_obj: None, - bindings: Rc::new(RefCell::new(HashMap::new())), + bindings: Rc::new(HashMap::new()), })) } - pub fn binding(&self, name: &str) -> Binding { + pub fn binding(&self, name: &str) -> LazyVal { self.0 .bindings - .borrow() .get(name) .cloned() .unwrap_or_else(|| { @@ -69,7 +68,7 @@ pub fn extend( &self, - new_bindings: HashMap, + new_bindings: HashMap, new_dollar: Option, new_this: Option, new_super_obj: Option, @@ -80,11 +79,14 @@ let bindings = if new_bindings.is_empty() { self.0.bindings.clone() } else { - let new = self.0.bindings.clone(); + let mut new = HashMap::new(); // = self.0.bindings.clone(); + for (k, v) in self.0.bindings.iter() { + new.insert(k.clone(), v.clone()); + } for (k, v) in new_bindings.into_iter() { - new.borrow_mut().insert(k, v); + new.insert(k, v.0(this.clone(), super_obj.clone())); } - new + Rc::new(new) }; Context(Rc::new(ContextInternals { dollar, --- a/crates/jsonnet-evaluator/src/evaluate.rs +++ b/crates/jsonnet-evaluator/src/evaluate.rs @@ -1,6 +1,7 @@ use crate::{ - binding, bool_val, context_creator, function_default, function_rhs, future_wrapper, lazy_val, - Binding, Context, ContextCreator, FuncDesc, ObjMember, ObjValue, Val, + binding, bool_val, context_creator, function_default, function_rhs, future_wrapper, + lazy_binding, lazy_val, Binding, Context, ContextCreator, FuncDesc, LazyBinding, ObjMember, + ObjValue, Val, }; use closure::closure; use jsonnet_parser::{ @@ -12,30 +13,28 @@ rc::Rc, }; -pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (String, Binding) { +pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (String, LazyBinding) { let b = b.clone(); if let Some(args) = &b.params { let args = args.clone(); ( b.name.clone(), - binding!(move |this, super_obj| Val::Lazy(lazy_val!( + lazy_binding!(move |this, super_obj| lazy_val!( closure!(clone b, clone args, clone context_creator, || evaluate_method( context_creator.0(this.clone(), super_obj.clone()), &b.value, args.clone() )) - ))), + )), ) } else { ( b.name.clone(), - binding!(move |this, super_obj| { - Val::Lazy(lazy_val!( - closure!(clone context_creator, clone b, || evaluate( - context_creator.0(this.clone(), super_obj.clone()), - &b.value - )) - )) + lazy_binding!(move |this, super_obj| { + lazy_val!(closure!(clone context_creator, clone b, || evaluate( + context_creator.0(this.clone(), super_obj.clone()), + &b.value + ))) }), ) } @@ -87,6 +86,19 @@ (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::Literal(LiteralType::False), BinaryOpType::And, Val::Literal(LiteralType::False)) => { + bool_val(false) + } + (Val::Literal(LiteralType::False), BinaryOpType::And, Val::Literal(LiteralType::True)) => { + bool_val(false) + } + (Val::Literal(LiteralType::True), BinaryOpType::And, Val::Literal(LiteralType::False)) => { + bool_val(false) + } + (Val::Literal(LiteralType::True), BinaryOpType::And, Val::Literal(LiteralType::True)) => { + bool_val(true) + } + (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()), @@ -126,37 +138,39 @@ } } -future_wrapper!(HashMap, FutureNewBindings); +future_wrapper!(HashMap, FutureNewBindings); future_wrapper!(ObjValue, FutureObjValue); // TODO: Asserts pub fn evaluate_object(context: Context, object: ObjBody) -> ObjValue { match object { ObjBody::MemberList(members) => { - let future_bindings = FutureNewBindings::new(); + let new_bindings = FutureNewBindings::new(); let future_this = FutureObjValue::new(); let context_creator = context_creator!( - closure!(clone context, clone future_bindings, clone future_this, |this: Option, super_obj: Option| { + closure!(clone context, clone new_bindings, clone future_this, |this: Option, super_obj: Option| { context.clone().extend( - future_bindings.clone().unwrap(), + new_bindings.clone().unwrap(), context.clone().dollar().clone().or_else(||this.clone()), - Some(future_this.clone().unwrap()), + Some(this.clone().unwrap()), super_obj ) }) ); - let mut bindings: HashMap = 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, context_creator.clone())) { - bindings.insert(n, b); + let mut bindings: HashMap = 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, context_creator.clone())) + { + bindings.insert(n, b); + } + new_bindings.fill(bindings); } - future_bindings.fill(bindings); let mut new_members = BTreeMap::new(); for member in members.into_iter() { @@ -176,11 +190,12 @@ visibility: visibility.clone(), invoke: binding!( closure!(clone value, clone context_creator, |this, super_obj| { + let context = context_creator.0(this, super_obj); // TODO: Assert evaluate( - context_creator.0(this, super_obj), + context, &value, - ) + ).unwrap_if_lazy() }) ), }, @@ -244,27 +259,39 @@ evaluate_binary_op(&evaluate(context.clone(), v1), *o, &evaluate(context, v2)) } UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)), - Var(name) => { - let variable = context.binding(&name); - variable.0(None, None).unwrap_if_lazy() - } + Var(name) => Val::Lazy(context.binding(&name)).unwrap_if_lazy(), Index(box value, box index) => { match ( evaluate(context.clone(), value).unwrap_if_lazy(), - evaluate(context, index), + evaluate(context.clone(), index), ) { (Val::Obj(v), Val::Str(s)) => v .get(&s) - .unwrap_or_else(|| panic!("{} not found in {:?}", s, v)), + .unwrap_or_else(closure!(clone context, || { + if let Some(n) = v.get("__intristic_namespace__") { + if let Val::Str(n) = n.unwrap_if_lazy() { + Val::Intristic(n, s) + } else { + panic!("__intristic_namespace__ should be string"); + } + } else { + panic!("{} not found in {:?}", s, v) + } + })) + .unwrap_if_lazy(), (Val::Arr(v), Val::Num(n)) => v .get(n as usize) .unwrap_or_else(|| panic!("out of bounds")) .clone(), + (Val::Str(s), Val::Num(n)) => { + // FIXME: Only works for ASCII + Val::Str(String::from_utf8(vec![s.as_bytes()[n as usize]]).unwrap()) + } (v, i) => todo!("not implemented: {:?}[{:?}]", v, i.unwrap_if_lazy()), } } LocalExpr(bindings, returned) => { - let mut new_bindings: HashMap = HashMap::new(); + let mut new_bindings: HashMap = HashMap::new(); let future_context = Context::new_future(); let context_creator = context_creator!( @@ -287,6 +314,49 @@ Apply(box value, ArgsDesc(args)) => { let value = evaluate(context.clone(), value).unwrap_if_lazy(); match value { + // TODO: Capture context of application + Val::Intristic(ns, name) => match (&ns as &str, &name as &str) { + ("std", "length") => { + assert_eq!(args.len(), 1); + let expr = &args.get(0).unwrap().1; + match evaluate(context, expr) { + Val::Str(n) => Val::Num(n.len() as f64), + Val::Arr(i) => Val::Num(i.len() as f64), + v => panic!("can't get length of {:?}", v), + } + } + ("std", "type") => { + assert_eq!(args.len(), 1); + let expr = &args.get(0).unwrap().1; + Val::Str(evaluate(context, expr).type_of().to_owned()) + } + ("std", "makeArray") => { + assert_eq!(args.len(), 2); + if let (Val::Num(v), Val::Func(d)) = ( + evaluate(context.clone(), &args[0].1), + evaluate(context, &args[1].1), + ) { + assert!(v > 0.0); + let mut out = Vec::with_capacity(v as usize); + for i in 0..v as usize { + out.push(d.evaluate(vec![(None, Val::Num(i as f64))])) + } + Val::Arr(out) + } else { + panic!("bad makeArray call"); + } + } + ("std", "codepoint") => { + assert_eq!(args.len(), 1); + if let Val::Str(s) = evaluate(context.clone(), &args[0].1) { + // FIXME: this is not a codepoint + Val::Num(s.as_bytes()[0] as f64) + } else { + panic!("bad codepoint call"); + } + } + (ns, name) => panic!("Intristic not found: {}.{}", ns, name), + }, Val::Func(f) => f.evaluate( args.clone() .into_iter() --- a/crates/jsonnet-evaluator/src/lib.rs +++ b/crates/jsonnet-evaluator/src/lib.rs @@ -20,6 +20,11 @@ binding, dyn Fn(Option, Option) -> Val ); +rc_fn_helper!( + LazyBinding, + lazy_binding, + dyn Fn(Option, Option) -> LazyVal +); rc_fn_helper!(FunctionRhs, function_rhs, dyn Fn(Context) -> Val); rc_fn_helper!( FunctionDefault, @@ -39,10 +44,10 @@ } macro_rules! eval_stdlib { - ($str: expr) => { + ($str: expr) => {{ let std = "local std = ".to_owned() + jsonnet_stdlib::STDLIB_STR + ";"; evaluate(Context::new(), &parse(&(std + $str)).unwrap()) - }; + }}; } macro_rules! assert_eval { @@ -61,6 +66,14 @@ ) }; } + macro_rules! assert_json_stdlib { + ($str: expr, $out: expr) => { + assert_eq!( + format!("{}", eval_stdlib!($str)), + $out + ) + }; + } macro_rules! assert_eval_neg { ($str: expr) => { assert_eq!( @@ -110,7 +123,7 @@ #[test] fn object_inheritance() { - assert_json!("{a:self.b} + {b:3}", r#"{"a":3,"b":3}"#); + assert_json!("{a: self.b} + {b:3}", r#"{"a":3,"b":3}"#); } #[test] @@ -194,7 +207,8 @@ eval_stdlib!( r#"{ local me = self, - b: me, + a: 3, + b: me.a, }.b"# ); } @@ -212,7 +226,15 @@ } #[test] + fn string_is_string() { + assert_eq!( + eval_stdlib!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"), + Val::Literal(LiteralType::False) + ); + } + + #[test] fn base64_works() { - eval_stdlib!(r#"std.base64("test")"#); + assert_json_stdlib!(r#"std.base64("test")"#, r#""dGVzdA==""#); } } --- a/crates/jsonnet-evaluator/src/obj.rs +++ b/crates/jsonnet-evaluator/src/obj.rs @@ -70,17 +70,17 @@ } pub fn get(&self, key: &str) -> Option { // TODO: Cache get_raw result - self.get_raw(key, Some(self)) + self.get_raw(key, self) } - fn get_raw(&self, key: &str, real_this: Option<&ObjValue>) -> Option { + fn get_raw(&self, key: &str, real_this: &ObjValue) -> Option { match (self.0.this_entries.get(key), &self.0.super_obj) { (Some(k), None) => Some(k.invoke.0( - real_this.as_ref().map(|e| (*e).clone()), + Some(real_this.clone()), self.0.super_obj.clone(), )), (Some(k), Some(s)) => { let our = k.invoke.0( - real_this.as_ref().map(|e| (*e).clone()), + Some(real_this.clone()), self.0.super_obj.clone(), ); if k.add { --- a/crates/jsonnet-evaluator/src/val.rs +++ b/crates/jsonnet-evaluator/src/val.rs @@ -1,4 +1,6 @@ -use crate::{binding, rc_fn_helper, Binding, Context, FunctionDefault, FunctionRhs, ObjValue}; +use crate::{ + lazy_binding, rc_fn_helper, Context, FunctionDefault, FunctionRhs, LazyBinding, ObjValue, +}; use closure::closure; use jsonnet_parser::{LiteralType, ParamsDesc}; use std::{ @@ -18,7 +20,7 @@ impl FuncDesc { // TODO: Check for unset variables pub fn evaluate(&self, args: Vec<(Option, Val)>) -> Val { - let mut new_bindings: HashMap = HashMap::new(); + let mut new_bindings: HashMap = HashMap::new(); let future_ctx = Context::new_future(); // self.params @@ -37,8 +39,8 @@ for (name, val) in args.clone().into_iter().filter(|e| e.0.is_some()) { new_bindings.insert( name.as_ref().unwrap().clone(), - binding!( - closure!(clone val, |_, _| Val::Lazy(lazy_val!(closure!(clone val, || val.clone())))) + lazy_binding!( + closure!(clone val, |_, _| lazy_val!(closure!(clone val, || val.clone()))) ), ); } @@ -46,8 +48,8 @@ if let Some((None, val)) = args.get(i) { new_bindings.insert( param.0.clone(), - binding!( - closure!(clone val, |_, _| Val::Lazy(lazy_val!(closure!(clone val, || val.clone())))) + lazy_binding!( + closure!(clone val, |_, _| lazy_val!(closure!(clone val, || val.clone()))) ), ); } -- gitstuff