From fab41c8db644a692bd36beb5a46dcc64e02bd2be Mon Sep 17 00:00:00 2001 From: Лач Date: Wed, 03 Jun 2020 16:13:09 +0000 Subject: [PATCH] feat(evaluator): use errors, pass EvaluationState --- --- a/crates/jsonnet-evaluator/Cargo.toml +++ b/crates/jsonnet-evaluator/Cargo.toml @@ -9,6 +9,4 @@ [dependencies] jsonnet-parser = { path = "../jsonnet-parser" } closure = "0.3.0" - -[dev-dependencies] jsonnet-stdlib = { version = "0.1.0", path = "../jsonnet-stdlib" } --- a/crates/jsonnet-evaluator/src/ctx.rs +++ b/crates/jsonnet-evaluator/src/ctx.rs @@ -1,5 +1,6 @@ use crate::{ - future_wrapper, lazy_binding, lazy_val, rc_fn_helper, LazyBinding, LazyVal, ObjValue, Val, + future_wrapper, lazy_binding, lazy_val, rc_fn_helper, LazyBinding, LazyVal, ObjValue, Result, + Val, }; use closure::closure; use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; @@ -7,7 +8,7 @@ rc_fn_helper!( ContextCreator, context_creator, - dyn Fn(Option, Option) -> Context + dyn Fn(Option, Option) -> Result ); future_wrapper!(Context, FutureContext); @@ -65,12 +66,12 @@ ctx.unwrap() } - pub fn with_var(&self, name: String, value: Val) -> Context { + pub fn with_var(&self, name: String, value: Val) -> Result { let mut new_bindings: HashMap<_, LazyBinding> = HashMap::new(); new_bindings.insert( name, lazy_binding!( - closure!(clone value, |_t, _s|lazy_val!(closure!(clone value, ||value.clone()))) + closure!(clone value, |_t, _s|Ok(lazy_val!(closure!(clone value, ||Ok(value.clone()))))) ), ); self.extend(new_bindings, None, None, None) @@ -82,7 +83,7 @@ new_dollar: Option, new_this: Option, new_super_obj: Option, - ) -> Context { + ) -> Result { let dollar = new_dollar.or_else(|| self.0.dollar.clone()); let this = new_this.or_else(|| self.0.this.clone()); let super_obj = new_super_obj.or_else(|| self.0.super_obj.clone()); @@ -94,16 +95,16 @@ new.insert(k.clone(), v.clone()); } for (k, v) in new_bindings.into_iter() { - new.insert(k, v.0(this.clone(), super_obj.clone())); + new.insert(k, v.0(this.clone(), super_obj.clone())?); } Rc::new(new) }; - Context(Rc::new(ContextInternals { + Ok(Context(Rc::new(ContextInternals { dollar, this, super_obj, bindings, - })) + }))) } } --- a/crates/jsonnet-evaluator/src/error.rs +++ b/crates/jsonnet-evaluator/src/error.rs @@ -1 +1,20 @@ -pub enum Error {} +use crate::ValType; +use jsonnet_parser::LocExpr; + +#[derive(Debug)] +pub enum Error { + VariableIsNotDefined(String), + TypeMismatch(&'static str, Vec, ValType), + NoSuchField(String), + + RuntimeError(String), +} + +#[derive(Clone, Debug)] +pub struct StackTraceElement(pub LocExpr, pub String); +#[derive(Debug)] +pub struct StackTrace(pub Vec); + +#[derive(Debug)] +pub struct LocError(pub Error, pub StackTrace); +pub type Result = std::result::Result; --- a/crates/jsonnet-evaluator/src/evaluate.rs +++ b/crates/jsonnet-evaluator/src/evaluate.rs @@ -1,7 +1,7 @@ use crate::{ - binding, bool_val, context_creator, function_default, function_rhs, future_wrapper, - lazy_binding, lazy_val, Context, ContextCreator, EvaluationState, FuncDesc, LazyBinding, - ObjMember, ObjValue, Val, + binding, context_creator, create_error, function_default, function_rhs, future_wrapper, + lazy_binding, lazy_val, push, Context, ContextCreator, FuncDesc, LazyBinding, ObjMember, + ObjValue, Result, Val, }; use closure::closure; use jsonnet_parser::{ @@ -14,89 +14,66 @@ rc::Rc, }; -pub fn evaluate_binding( - eval_state: EvaluationState, - b: &BindSpec, - context_creator: ContextCreator, -) -> (String, LazyBinding) { +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(), - lazy_binding!(move |this, super_obj| lazy_val!( - closure!(clone b, clone args, clone context_creator, clone eval_state, || evaluate_method( - context_creator.0(this.clone(), super_obj.clone()), - eval_state.clone(), + lazy_binding!(move |this, super_obj| Ok(lazy_val!( + closure!(clone b, clone args, clone context_creator, || Ok(evaluate_method( + context_creator.0(this.clone(), super_obj.clone())?, &b.value, args.clone() - )) - )), + ))) + ))), ) } else { ( b.name.clone(), lazy_binding!(move |this, super_obj| { - lazy_val!( - closure!(clone context_creator, clone b, clone eval_state, || evaluate( - context_creator.0(this.clone(), super_obj.clone()), - eval_state.clone(), + Ok(lazy_val!( + closure!(clone context_creator, clone b, || evaluate( + context_creator.0(this.clone(), super_obj.clone())?, &b.value )) - ) + )) }), ) } } -pub fn evaluate_method( - ctx: Context, - eval_state: EvaluationState, - expr: &LocExpr, - arg_spec: ParamsDesc, -) -> Val { +pub fn evaluate_method(ctx: Context, expr: &LocExpr, arg_spec: ParamsDesc) -> Val { Val::Func(FuncDesc { ctx, params: arg_spec, - eval_rhs: function_rhs!( - closure!(clone expr, clone eval_state, |ctx| evaluate(ctx, eval_state.clone(), &expr)) - ), - eval_default: function_default!( - closure!(clone eval_state, |ctx, default| evaluate(ctx, eval_state.clone(), &default)) - ), + eval_rhs: function_rhs!(closure!(clone expr, |ctx| evaluate(ctx, &expr))), + eval_default: function_default!(closure!(|ctx, default| evaluate(ctx, &default))), }) } pub fn evaluate_field_name( context: Context, - eval_state: EvaluationState, field_name: &jsonnet_parser::FieldName, -) -> String { - match field_name { +) -> Result { + Ok(match field_name { jsonnet_parser::FieldName::Fixed(n) => n.clone(), jsonnet_parser::FieldName::Dyn(expr) => { - let name = evaluate(context, eval_state, expr).unwrap_if_lazy(); - match name { - Val::Str(n) => n, - _ => panic!( - "dynamic field name can be only evaluated to 'string', got: {:?}", - name - ), - } + evaluate(context, expr)?.try_cast_str("dynamic field name")? } - } + }) } -pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Val { - match (op, b) { - (o, Val::Lazy(l)) => evaluate_unary_op(o, &l.evaluate()), +pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result { + Ok(match (op, b) { + (o, Val::Lazy(l)) => evaluate_unary_op(o, &l.evaluate()?)?, (UnaryOpType::Not, Val::Bool(v)) => Val::Bool(!v), (op, o) => panic!("unary op not implemented: {:?} {:?}", op, o), - } + }) } -pub fn evaluate_add_op(a: &Val, b: &Val) -> Val { - match (a, b) { +pub(crate) fn evaluate_add_op(a: &Val, b: &Val) -> Result { + Ok(match (a, b) { (Val::Str(v1), Val::Str(v2)) => Val::Str(v1.to_owned() + &v2), (Val::Str(v1), Val::Num(v2)) => Val::Str(format!("{}{}", v1, v2)), (Val::Num(v1), Val::Str(v2)) => Val::Str(format!("{}{}", v1, v2)), @@ -104,36 +81,29 @@ (Val::Arr(a), Val::Arr(b)) => Val::Arr([&a[..], &b[..]].concat()), (Val::Num(v1), Val::Num(v2)) => Val::Num(v1 + v2), _ => panic!("can't add: {:?} and {:?}", a, b), - } + }) } -pub fn evaluate_binary_op( - context: Context, - eval_state: EvaluationState, - a: &Val, - op: BinaryOpType, - b: &Val, -) -> Val { - match (a, op, b) { - (Val::Lazy(a), o, b) => evaluate_binary_op(context, eval_state, &a.evaluate(), o, b), - (a, o, Val::Lazy(b)) => evaluate_binary_op(context, eval_state, a, o, &b.evaluate()), +pub fn evaluate_binary_op(context: Context, a: &Val, op: BinaryOpType, b: &Val) -> Result { + Ok(match (a, op, b) { + (Val::Lazy(a), o, b) => evaluate_binary_op(context, &a.evaluate()?, o, b)?, + (a, o, Val::Lazy(b)) => evaluate_binary_op(context, a, o, &b.evaluate()?)?, - (a, BinaryOpType::Add, b) => evaluate_add_op(a, b), + (a, BinaryOpType::Add, b) => evaluate_add_op(a, b)?, - (Val::Str(v1), BinaryOpType::Ne, Val::Str(v2)) => bool_val(v1 != v2), + (Val::Str(v1), BinaryOpType::Ne, Val::Str(v2)) => Val::Bool(v1 != v2), (Val::Str(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Str(v1.repeat(*v2 as usize)), (Val::Str(format), BinaryOpType::Mod, args) => evaluate( context - .with_var("__tmp__format__".to_owned(), Val::Str(format.to_owned())) + .with_var("__tmp__format__".to_owned(), Val::Str(format.to_owned()))? .with_var( "__tmp__args__".to_owned(), match args { Val::Arr(v) => Val::Arr(v.clone()), v => Val::Arr(vec![v.clone()]), }, - ), - eval_state, + )?, &el!(Expr::Apply( el!(Expr::Index( el!(Expr::Var("std".to_owned())), @@ -144,7 +114,7 @@ Arg(None, el!(Expr::Var("__tmp__args__".to_owned()))) ]) )), - ), + )?, (Val::Bool(a), BinaryOpType::And, Val::Bool(b)) => Val::Bool(*a && *b), (Val::Bool(a), BinaryOpType::Or, Val::Bool(b)) => Val::Bool(*a || *b), @@ -162,13 +132,13 @@ 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::Lt, Val::Num(v2)) => Val::Bool(v1 < v2), + (Val::Num(v1), BinaryOpType::Gt, Val::Num(v2)) => Val::Bool(v1 > v2), + (Val::Num(v1), BinaryOpType::Lte, Val::Num(v2)) => Val::Bool(v1 <= v2), + (Val::Num(v1), BinaryOpType::Gte, Val::Num(v2)) => Val::Bool(v1 >= v2), - (Val::Num(v1), BinaryOpType::Eq, Val::Num(v2)) => bool_val((v1 - v2).abs() < f64::EPSILON), - (Val::Num(v1), BinaryOpType::Ne, Val::Num(v2)) => bool_val((v1 - v2).abs() > f64::EPSILON), + (Val::Num(v1), BinaryOpType::Eq, Val::Num(v2)) => Val::Bool((v1 - v2).abs() < f64::EPSILON), + (Val::Num(v1), BinaryOpType::Ne, Val::Num(v2)) => Val::Bool((v1 - v2).abs() > f64::EPSILON), (Val::Num(v1), BinaryOpType::BitAnd, Val::Num(v2)) => { Val::Num(((*v1 as i32) & (*v2 as i32)) as f64) @@ -179,10 +149,10 @@ (Val::Num(v1), BinaryOpType::BitXor, Val::Num(v2)) => { Val::Num(((*v1 as i32) ^ (*v2 as i32)) as f64) } - (a, BinaryOpType::Eq, b) => bool_val(a == b), - (a, BinaryOpType::Ne, b) => bool_val(a != b), + (a, BinaryOpType::Eq, b) => Val::Bool(a == b), + (a, BinaryOpType::Ne, b) => Val::Bool(a != b), _ => panic!("no rules for binary operation: {:?} {:?} {:?}", a, op, b), - } + }) } future_wrapper!(HashMap, FutureNewBindings); @@ -190,54 +160,52 @@ pub fn evaluate_comp( context: Context, - eval_state: EvaluationState, value: &LocExpr, specs: &[CompSpec], -) -> Option> { - match specs.get(0) { - None => Some(vec![evaluate(context, eval_state, &value)]), +) -> Result>> { + Ok(match specs.get(0) { + None => Some(vec![evaluate(context, &value)?]), Some(CompSpec::IfSpec(IfSpecData(cond))) => { - match evaluate(context.clone(), eval_state.clone(), &cond).unwrap_if_lazy() { - Val::Bool(false) => None, - Val::Bool(true) => evaluate_comp(context, eval_state, value, &specs[1..]), - _ => panic!("if expression evaluated to non-boolean value"), + if evaluate(context.clone(), &cond)?.try_cast_bool("if spec")? { + evaluate_comp(context, value, &specs[1..])? + } else { + None } } Some(CompSpec::ForSpec(ForSpecData(var, expr))) => { - match evaluate(context.clone(), eval_state.clone(), &expr).unwrap_if_lazy() { + match evaluate(context.clone(), &expr)?.unwrap_if_lazy()? { Val::Arr(list) => { let mut out = Vec::new(); for item in list { let item = item.clone(); out.push(evaluate_comp( - context.with_var(var.clone(), item), - eval_state.clone(), + context.with_var(var.clone(), item)?, value, &specs[1..], - )); + )?); } Some(out.iter().flatten().flatten().cloned().collect()) } _ => panic!("for expression evaluated to non-iterable value"), } } - } + }) } // TODO: Asserts -pub fn evaluate_object(context: Context, eval_state: EvaluationState, object: ObjBody) -> ObjValue { - match object { +pub fn evaluate_object(context: Context, object: ObjBody) -> Result { + Ok(match object { ObjBody::MemberList(members) => { let new_bindings = FutureNewBindings::new(); let future_this = FutureObjValue::new(); let context_creator = context_creator!( closure!(clone context, clone new_bindings, clone future_this, |this: Option, super_obj: Option| { - context.clone().extend( + Ok(context.clone().extend( new_bindings.clone().unwrap(), context.clone().dollar().clone().or_else(||this.clone()), Some(this.unwrap()), super_obj - ) + )?) }) ); { @@ -248,7 +216,7 @@ Member::BindStmt(b) => Some(b.clone()), _ => None, }) - .map(|b| evaluate_binding(eval_state.clone(), &b, context_creator.clone())) + .map(|b| evaluate_binding(&b, context_creator.clone())) { bindings.insert(n, b); } @@ -265,21 +233,20 @@ visibility, value, }) => { - let name = evaluate_field_name(context.clone(), eval_state.clone(), &name); + let name = evaluate_field_name(context.clone(), &name)?; new_members.insert( name, ObjMember { add: plus, visibility: visibility.clone(), invoke: binding!( - closure!(clone value, clone context_creator, clone eval_state, |this, super_obj| { - let context = context_creator.0(this, super_obj); + closure!(clone value, clone context_creator, |this, super_obj| { + let context = context_creator.0(this, super_obj)?; // TODO: Assert evaluate( context, - eval_state.clone(), &value, - ).unwrap_if_lazy() + )?.unwrap_if_lazy() }) ), }, @@ -291,21 +258,20 @@ value, .. }) => { - let name = evaluate_field_name(context.clone(), eval_state.clone(), &name); + let name = evaluate_field_name(context.clone(), &name)?; new_members.insert( name, ObjMember { add: false, visibility: Visibility::Hidden, invoke: binding!( - closure!(clone value, clone context_creator, clone eval_state, |this, super_obj| { + closure!(clone value, clone context_creator, |this, super_obj| { // TODO: Assert - evaluate_method( - context_creator.0(this, super_obj), - eval_state.clone(), + Ok(evaluate_method( + context_creator.0(this, super_obj)?, &value.clone(), params.clone(), - ) + )) }) ), }, @@ -318,14 +284,14 @@ future_this.fill(ObjValue::new(None, Rc::new(new_members))) } _ => todo!(), - } + }) } -pub fn evaluate(context: Context, eval_state: EvaluationState, expr: &LocExpr) -> Val { +pub fn evaluate(context: Context, expr: &LocExpr) -> Result { use Expr::*; - eval_state.clone().push(expr.clone(), "expr".to_owned(), || { + push(expr.clone(), "expr".to_owned(), || { let LocExpr(expr, loc) = expr; - match &**expr { + Ok(match &**expr { Literal(LiteralType::This) => Val::Obj( context .this() @@ -341,42 +307,31 @@ Literal(LiteralType::True) => Val::Bool(true), Literal(LiteralType::False) => Val::Bool(false), Literal(LiteralType::Null) => Val::Null, - Parened(e) => evaluate(context, eval_state.clone(), e), + Parened(e) => evaluate(context, e)?, Str(v) => Val::Str(v.clone()), Num(v) => Val::Num(*v), BinaryOp(v1, o, v2) => { - let a = evaluate(context.clone(), eval_state.clone(), v1).unwrap_if_lazy(); + let a = evaluate(context.clone(), v1)?.unwrap_if_lazy()?; let op = *o; - let b = evaluate(context.clone(), eval_state.clone(), v2).unwrap_if_lazy(); - evaluate_binary_op( - context, - eval_state, - &a, - op, - &b, - ) - }, - UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, eval_state, v)), - Var(name) => Val::Lazy(context.binding(&name)).unwrap_if_lazy(), + let b = evaluate(context.clone(), v2)?.unwrap_if_lazy()?; + evaluate_binary_op(context, &a, op, &b)? + } + UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)?)?, + Var(name) => Val::Lazy(context.binding(&name)).unwrap_if_lazy()?, Index(value, index) => { match ( - evaluate(context.clone(), eval_state.clone(), value).unwrap_if_lazy(), - evaluate(context.clone(), eval_state.clone(), index), + evaluate(context.clone(), value)?.unwrap_if_lazy()?, + evaluate(context.clone(), index)?, ) { - (Val::Obj(v), Val::Str(s)) => v - .get(&s) - .unwrap_or_else(closure!(clone context, clone eval_state, || { - 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::Obj(v), Val::Str(s)) => { + if let Some(v) = v.get(&s)? { + v.unwrap_if_lazy()? + } else if let Some(Val::Str(n)) = v.get("__intristic_namespace__")? { + Val::Intristic(n, s) + } else { + create_error(crate::Error::NoSuchField(s))? + } + } (Val::Arr(v), Val::Num(n)) => v .get(n as usize) .unwrap_or_else(|| panic!("out of bounds")) @@ -392,34 +347,35 @@ let future_context = Context::new_future(); let context_creator = context_creator!( - closure!(clone future_context, |_, _| future_context.clone().unwrap()) + closure!(clone future_context, |_, _| Ok(future_context.clone().unwrap())) ); for (k, v) in bindings .iter() - .map(|b| evaluate_binding(eval_state.clone(), b, context_creator.clone())) + .map(|b| evaluate_binding(b, context_creator.clone())) { new_bindings.insert(k, v); } let context = context - .extend(new_bindings, None, None, None) + .extend(new_bindings, None, None, None)? .into_future(future_context); - evaluate(context, eval_state.clone(), &returned.clone()) + evaluate(context, &returned.clone())? } Arr(items) => { let mut out = Vec::with_capacity(items.len()); for item in items { - out.push(evaluate(context.clone(), eval_state.clone(), item)); + out.push(evaluate(context.clone(), item)?); } Val::Arr(out) } - ArrComp(expr, compspecs) => { - Val::Arr(evaluate_comp(context, eval_state, expr, compspecs).unwrap()) - } - Obj(body) => Val::Obj(evaluate_object(context, eval_state, body.clone())), + ArrComp(expr, compspecs) => Val::Arr( + // First compspec should be forspec, so no "None" possible here + evaluate_comp(context, expr, compspecs)?.unwrap(), + ), + Obj(body) => Val::Obj(evaluate_object(context, body.clone())?), Apply(value, ArgsDesc(args)) => { - let value = evaluate(context.clone(), eval_state.clone(), value).unwrap_if_lazy(); + 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) { @@ -427,7 +383,7 @@ ("std", "length") => { assert_eq!(args.len(), 1); let expr = &args.get(0).unwrap().1; - match evaluate(context, eval_state.clone(), expr) { + match evaluate(context, expr)? { Val::Str(n) => Val::Num(n.chars().count() as f64), Val::Arr(i) => Val::Num(i.len() as f64), v => panic!("can't get length of {:?}", v), @@ -437,19 +393,19 @@ ("std", "type") => { assert_eq!(args.len(), 1); let expr = &args.get(0).unwrap().1; - Val::Str(evaluate(context, eval_state, expr).type_of().to_owned()) + Val::Str(evaluate(context, expr)?.value_type()?.name().to_owned()) } // length, idx=>any ("std", "makeArray") => { assert_eq!(args.len(), 2); if let (Val::Num(v), Val::Func(d)) = ( - evaluate(context.clone(), eval_state.clone(), &args[0].1), - evaluate(context, eval_state, &args[1].1), + 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))])) + out.push(d.evaluate(vec![(None, Val::Num(i as f64))])?) } Val::Arr(out) } else { @@ -459,7 +415,7 @@ // string ("std", "codepoint") => { assert_eq!(args.len(), 1); - if let Val::Str(s) = evaluate(context, eval_state, &args[0].1) { + if let Val::Str(s) = evaluate(context, &args[0].1)? { assert!( s.chars().count() == 1, "std.codepoint should receive single char string" @@ -473,8 +429,8 @@ ("std", "objectFieldsEx") => { assert_eq!(args.len(), 2); if let (Val::Obj(body), Val::Bool(_include_hidden)) = ( - evaluate(context.clone(), eval_state.clone(), &args[0].1), - evaluate(context, eval_state, &args[1].1), + evaluate(context.clone(), &args[0].1)?, + evaluate(context, &args[1].1)?, ) { // TODO: handle visibility (_include_hidden) Val::Arr(body.fields().into_iter().map(Val::Str).collect()) @@ -491,44 +447,55 @@ ( a.clone().0, Val::Lazy(lazy_val!( - closure!(clone context, clone a, clone eval_state, || evaluate(context.clone(), eval_state.clone(), &a.clone().1)) + closure!(clone context, clone a, || evaluate(context.clone(), &a.clone().1)) )), ) }) .collect(), - ), + )?, _ => panic!("{:?} is not a function", value), } } - Function(params, body) => evaluate_method(context, eval_state, body, params.clone()), + Function(params, body) => evaluate_method(context, body, params.clone()), AssertExpr(AssertStmt(value, msg), returned) => { - if evaluate(context.clone(), eval_state.clone(), &value).try_cast_bool() { - evaluate(context, eval_state, returned) - }else { - if let Some(msg) = msg { - panic!("assertion failed ({:?}): {}", value, evaluate(context, eval_state, msg).try_cast_str()); - } else { - panic!("assertion failed ({:?}): no message", value); - } + if evaluate(context.clone(), &value)? + .try_cast_bool("assertion condition should be boolean")? + { + evaluate(context, returned)? + } else if let Some(msg) = msg { + panic!( + "assertion failed ({:?}): {}", + value, + evaluate(context, msg)? + .try_cast_str("assertion message should be string")? + ); + } else { + panic!("assertion failed ({:?}): no message", value); } - }, - Error(e) => panic!("error: {}", evaluate(context, eval_state, e)), + } + Error(e) => create_error(crate::Error::RuntimeError( + evaluate(context, e)?.try_cast_str("error text should be string")?, + ))?, IfElse { cond, cond_then, cond_else, - } => match evaluate(context.clone(), eval_state.clone(), &cond.0).unwrap_if_lazy() { - Val::Bool(true) => evaluate(context, eval_state.clone(), cond_then), - Val::Bool(false) => match cond_else { - Some(v) => evaluate(context, eval_state, v), - None => Val::Bool(false), - }, - v => panic!("if condition evaluated to {:?} (boolean needed instead)", v), - }, + } => { + if evaluate(context.clone(), &cond.0)? + .try_cast_bool("if condition should be boolean")? + { + evaluate(context, cond_then)? + } else { + match cond_else { + Some(v) => evaluate(context, v)?, + None => Val::Bool(false), + } + } + } _ => panic!( "evaluation not implemented: {:?}", LocExpr(expr.clone(), loc.clone()) ), - } + }) }) } --- a/crates/jsonnet-evaluator/src/lib.rs +++ b/crates/jsonnet-evaluator/src/lib.rs @@ -22,41 +22,59 @@ rc_fn_helper!( Binding, binding, - dyn Fn(Option, Option) -> Val + dyn Fn(Option, Option) -> Result ); rc_fn_helper!( LazyBinding, lazy_binding, - dyn Fn(Option, Option) -> LazyVal + dyn Fn(Option, Option) -> Result ); -rc_fn_helper!(FunctionRhs, function_rhs, dyn Fn(Context) -> Val); +rc_fn_helper!(FunctionRhs, function_rhs, dyn Fn(Context) -> Result); rc_fn_helper!( FunctionDefault, function_default, - dyn Fn(Context, LocExpr) -> Val + dyn Fn(Context, LocExpr) -> Result ); +pub struct FileData(String, LocExpr, Option); #[derive(Default)] pub struct EvaluationStateInternals { /// Used for stack-overflows and stacktraces - stack: RefCell>, + stack: RefCell>, /// Contains file source codes and evaluated results for imports and pretty printing stacktraces - files: RefCell)>>, + files: RefCell>, globals: RefCell>, } +thread_local! { + pub static EVAL_STATE: RefCell> = RefCell::new(None) +} +pub(crate) fn with_state(f: impl FnOnce(&EvaluationState) -> T) -> T { + EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap())) +} +pub(crate) fn create_error(err: Error) -> Result { + with_state(|s| s.error(err)) +} +pub(crate) fn push(e: LocExpr, comment: String, f: impl FnOnce() -> T) -> T { + with_state(|s| s.push(e, comment, f)) +} + #[derive(Default, Clone)] pub struct EvaluationState(Rc); impl EvaluationState { - pub fn add_file(&self, name: String, code: String) -> Result<(), Box> { + pub fn add_file( + &self, + name: String, + code: String, + ) -> std::result::Result<(), Box> { self.0.files.borrow_mut().insert( name.clone(), - ( + FileData( code.clone(), parse( &code, &ParserSettings { - file_name: name.clone(), + file_name: name, loc_data: true, }, )?, @@ -66,7 +84,8 @@ Ok(()) } - pub fn evaluate_file(&self, name: &str) -> Result> { + pub fn evaluate_file(&self, name: &str) -> Result { + self.begin_state(); let expr: LocExpr = { let ro_map = self.0.files.borrow(); let value = ro_map @@ -77,7 +96,7 @@ } value.1.clone() }; - let value = evaluate(self.create_default_context(), self.clone(), &expr); + let value = evaluate(self.create_default_context()?, &expr)?; { self.0 .files @@ -87,10 +106,11 @@ .2 .replace(value.clone()); } + self.end_state(); Ok(value) } - pub fn parse_evaluate_raw(&self, code: &str) -> Val { + pub fn parse_evaluate_raw(&self, code: &str) -> Result { let parsed = parse( &code, &ParserSettings { @@ -98,29 +118,30 @@ loc_data: true, }, ); - evaluate( - self.create_default_context(), - self.clone(), - &parsed.unwrap(), - ) + self.begin_state(); + let value = evaluate(self.create_default_context()?, &parsed.unwrap()); + self.end_state(); + value } pub fn add_stdlib(&self) { + self.begin_state(); use jsonnet_stdlib::STDLIB_STR; self.add_file("std.jsonnet".to_owned(), STDLIB_STR.to_owned()) .unwrap(); let val = self.evaluate_file("std.jsonnet").unwrap(); self.0.globals.borrow_mut().insert("std".to_owned(), val); + self.end_state(); } - pub fn create_default_context(&self) -> Context { + pub fn create_default_context(&self) -> Result { let globals = self.0.globals.borrow(); let mut new_bindings: HashMap = HashMap::new(); for (name, value) in globals.iter() { new_bindings.insert( name.clone(), lazy_binding!( - closure!(clone value, |_self, _super_obj| lazy_val!(closure!(clone value, ||value.clone()))) + closure!(clone value, |_self, _super_obj| Ok(lazy_val!(closure!(clone value, ||Ok(value.clone()))))) ), ); } @@ -128,30 +149,37 @@ } pub fn push(&self, e: LocExpr, comment: String, f: impl FnOnce() -> T) -> T { - self.0.stack.borrow_mut().push((e, comment)); + self.0 + .stack + .borrow_mut() + .push(StackTraceElement(e, comment)); let result = f(); self.0.stack.borrow_mut().pop(); result } pub fn print_stack_trace(&self) { - for e in self.stack_trace() { + for e in self.stack_trace().0 { println!("{:?} - {:?}", e.0, e.1) } } - pub fn stack_trace(&self) -> Vec<(LocExpr, String)> { - self.0 - .stack - .borrow() - .iter() - .rev() - .map(|e| e.clone()) - .collect() + pub fn stack_trace(&self) -> StackTrace { + StackTrace(self.0.stack.borrow().iter().rev().cloned().collect()) + } + pub fn error(&self, err: Error) -> Result { + Err(LocError(err, self.stack_trace())) + } + + fn begin_state(&self) { + EVAL_STATE.with(|v| v.borrow_mut().replace(self.clone())); + } + fn end_state(&self) { + EVAL_STATE.with(|v| v.borrow_mut().take()); } } #[cfg(test)] pub mod tests { - use super::{evaluate, Context, Val}; + use super::Val; use crate::EvaluationState; use jsonnet_parser::*; @@ -176,7 +204,9 @@ let state = EvaluationState::default(); state.add_stdlib(); assert_eq!( - state.parse_evaluate_raw(r#"std.base64("test") == "dGVzdA==""#), + state + .parse_evaluate_raw(r#"std.assertEqual(std.base64("test"), "dGVzdA==w")"#) + .unwrap(), Val::Bool(true) ); } @@ -282,6 +312,7 @@ }; } + /* /// Sanity checking, before trusting to another tests #[test] fn equality_operator() { @@ -491,4 +522,5 @@ "# ); } + */ } --- a/crates/jsonnet-evaluator/src/obj.rs +++ b/crates/jsonnet-evaluator/src/obj.rs @@ -1,4 +1,4 @@ -use crate::{evaluate_add_op, Binding, Val}; +use crate::{evaluate_add_op, Binding, Result, Val}; use jsonnet_parser::Visibility; use std::{ cell::RefCell, @@ -32,13 +32,10 @@ write!(f, " + ")?; } let mut debug = f.debug_struct("ObjValue"); - debug.field("$ptr", &Rc::as_ptr(&self.0)); for (name, member) in self.0.this_entries.iter() { debug.field(name, member); } debug.finish_non_exhaustive() - // .field("fields", &self.fields()) - // .finish_non_exhaustive() } } @@ -71,33 +68,40 @@ } fields } - pub fn get(&self, key: &str) -> Option { + pub fn get(&self, key: &str) -> Result> { if let Some(v) = self.0.value_cache.borrow().get(key) { - return Some(v.clone()); + return Ok(Some(v.clone())); } - let v = self.get_raw(key, self).map(|v| v.unwrap_if_lazy()); - if let Some(v) = v.clone() { - self.0.value_cache.borrow_mut().insert(key.to_owned(), v); + if let Some(v) = self.get_raw(key, self)? { + let v = v.unwrap_if_lazy()?; + self.0 + .value_cache + .borrow_mut() + .insert(key.to_owned(), v.clone()); + Ok(Some(v)) + } else { + Ok(None) } - v } - fn get_raw(&self, key: &str, real_this: &ObjValue) -> Option { + fn get_raw(&self, key: &str, real_this: &ObjValue) -> Result> { match (self.0.this_entries.get(key), &self.0.super_obj) { - (Some(k), None) => Some(k.invoke.0( + (Some(k), None) => Ok(Some(k.invoke.0( Some(real_this.clone()), self.0.super_obj.clone(), - )), + )?)), (Some(k), Some(s)) => { - let our = k.invoke.0(Some(real_this.clone()), self.0.super_obj.clone()); + let our = k.invoke.0(Some(real_this.clone()), self.0.super_obj.clone())?; if k.add { - s.get_raw(key, real_this) - .map_or(Some(our.clone()), |v| Some(evaluate_add_op(&v, &our))) + s.get_raw(key, real_this)? + .map_or(Ok(Some(our.clone())), |v| { + Ok(Some(evaluate_add_op(&v, &our)?)) + }) } else { - Some(our) + Ok(Some(our)) } } (None, Some(s)) => s.get_raw(key, real_this), - (None, None) => None, + (None, None) => Ok(None), } } } --- a/crates/jsonnet-evaluator/src/val.rs +++ b/crates/jsonnet-evaluator/src/val.rs @@ -1,4 +1,7 @@ -use crate::{lazy_binding, Context, FunctionDefault, FunctionRhs, LazyBinding, ObjValue}; +use crate::{ + create_error, lazy_binding, Context, Error, FunctionDefault, FunctionRhs, LazyBinding, + ObjValue, Result, +}; use closure::closure; use jsonnet_parser::ParamsDesc; use std::{ @@ -9,28 +12,28 @@ }; struct LazyValInternals { - pub f: Box Val>, + pub f: Box Result>, pub cached: RefCell>, } #[derive(Clone)] pub struct LazyVal(Rc); impl LazyVal { - pub fn new(f: Box Val>) -> Self { + pub fn new(f: Box Result>) -> Self { LazyVal(Rc::new(LazyValInternals { f, cached: RefCell::new(None), })) } - pub fn evaluate(&self) -> Val { + pub fn evaluate(&self) -> Result { { let cached = self.0.cached.borrow(); if cached.is_some() { - return cached.clone().unwrap(); + return Ok(cached.clone().unwrap()); } } - let result = (self.0.f)(); + let result = (self.0.f)()?; self.0.cached.borrow_mut().replace(result.clone()); - result + Ok(result) } } #[macro_export] @@ -59,7 +62,7 @@ } impl FuncDesc { // TODO: Check for unset variables - pub fn evaluate(&self, args: Vec<(Option, Val)>) -> Val { + pub fn evaluate(&self, args: Vec<(Option, Val)>) -> Result { let mut new_bindings: HashMap = HashMap::new(); let future_ctx = Context::new_future(); @@ -80,7 +83,7 @@ new_bindings.insert( name.as_ref().unwrap().clone(), lazy_binding!( - closure!(clone val, |_, _| lazy_val!(closure!(clone val, || val.clone()))) + closure!(clone val, |_, _| Ok(lazy_val!(closure!(clone val, || Ok(val.clone()))))) ), ); } @@ -89,19 +92,49 @@ new_bindings.insert( param.0.clone(), lazy_binding!( - closure!(clone val, |_, _| lazy_val!(closure!(clone val, || val.clone()))) + closure!(clone val, |_, _| Ok(lazy_val!(closure!(clone val, || Ok(val.clone()))))) ), ); } } let ctx = self .ctx - .extend(new_bindings, None, None, None) + .extend(new_bindings, None, None, None)? .into_future(future_ctx); self.eval_rhs.0(ctx) } } +#[derive(Debug)] +pub enum ValType { + Bool, + Null, + Str, + Num, + Arr, + Obj, + Func, +} +impl ValType { + pub fn name(&self) -> &'static str { + use ValType::*; + match self { + Bool => "boolean", + Null => "null", + Str => "string", + Num => "number", + Arr => "array", + Obj => "object", + Func => "function", + } + } +} +impl Display for ValType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name()) + } +} + #[derive(Debug, PartialEq, Clone)] pub enum Val { Bool(bool), @@ -117,80 +150,44 @@ Intristic(String, String), } impl Val { - pub fn try_cast_bool(self) -> bool { - match self.unwrap_if_lazy() { - Val::Bool(v) => v, - v => panic!("expected bool, got {:?}", v), + pub fn try_cast_bool(self, context: &'static str) -> Result { + match self.unwrap_if_lazy()? { + Val::Bool(v) => Ok(v), + v => create_error(Error::TypeMismatch( + context, + vec![ValType::Bool], + v.value_type()?, + )), } } - pub fn try_cast_str(self) -> String { - match self.unwrap_if_lazy() { - Val::Str(v) => v, - v => panic!("expected bool, got {:?}", v), + pub fn try_cast_str(self, context: &'static str) -> Result { + match self.unwrap_if_lazy()? { + Val::Str(v) => Ok(v), + v => create_error(Error::TypeMismatch( + context, + vec![ValType::Str], + v.value_type()?, + )), } } - pub fn unwrap_if_lazy(self) -> Self { - if let Val::Lazy(v) = self { - v.evaluate().unwrap_if_lazy() + pub fn unwrap_if_lazy(self) -> Result { + Ok(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::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(&field).unwrap())?; - } - write!(f, "}}")?; - } - Val::Lazy(lazy) => { - write!(f, "{}", lazy.evaluate())?; - } - Val::Func(_) => { - write!(f, "<>")?; - } - v => panic!("no json equivalent for {:?}", v), - }; - Ok(()) + pub fn value_type(&self) -> Result { + Ok(match self { + Val::Str(..) => ValType::Str, + Val::Num(..) => ValType::Num, + Val::Arr(..) => ValType::Arr, + Val::Obj(..) => ValType::Obj, + Val::Func(..) => ValType::Func, + Val::Bool(_) => ValType::Bool, + Val::Null => ValType::Null, + Val::Intristic(_, _) => ValType::Func, + Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?, + }) } -} - -pub fn bool_val(v: bool) -> Val { - Val::Bool(v) } -- gitstuff