difftreelog
feat(evaluator) tailstrict calls
in: master
4 files changed
crates/jsonnet-evaluator/src/error.rsdiffbeforeafterboth9910 RuntimeError(String),10 RuntimeError(String),11 StackOverflow,11 StackOverflow,12 FractionalIndex,13 DivisionByZero,12}14}131514#[derive(Clone, Debug)]16#[derive(Clone, Debug)]crates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/evaluate.rs
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -135,7 +135,12 @@
// Num X Num
(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::Div, Val::Num(v2)) => {
+ if *v2 <= f64::EPSILON {
+ create_error(crate::Error::DivisionByZero)?
+ }
+ Val::Num(v1 / v2)
+ }
(Val::Num(v1), BinaryOpType::Sub, Val::Num(v2)) => Val::Num(v1 - v2),
@@ -305,6 +310,7 @@
})
}
+#[inline(always)]
pub fn evaluate(context: Context, expr: &LocExpr) -> Result<Val> {
use Expr::*;
let locexpr = expr.clone();
@@ -356,11 +362,15 @@
create_error(crate::Error::NoSuchField(s))?
}
}
- (Val::Arr(v), Val::Num(n)) => v
- .get(n as usize)
- .unwrap_or_else(|| panic!("out of bounds"))
- .clone()
- .unwrap_if_lazy()?,
+ (Val::Arr(v), Val::Num(n)) => {
+ if n.fract() > f64::EPSILON {
+ create_error(crate::Error::FractionalIndex)?
+ }
+ v.get(n as usize)
+ .unwrap_or_else(|| panic!("out of bounds"))
+ .clone()
+ .unwrap_if_lazy()?
+ }
(Val::Str(s), Val::Num(n)) => {
Val::Str(s.chars().skip(n as usize).take(1).collect())
}
@@ -511,29 +521,52 @@
panic!("bad trace call");
}
}
+ ("std", "pow") => {
+ assert_eq!(args.len(), 2);
+ if let (Val::Num(a), Val::Num(b)) = (
+ evaluate(context.clone(), &args[0].1)?,
+ evaluate(context, &args[1].1)?,
+ ) {
+ Val::Num(a.powf(b))
+ } else {
+ panic!("bad pow call");
+ }
+ }
(ns, name) => panic!("Intristic not found: {}.{}", ns, name),
},
- Val::Func(f) => push(locexpr, "function call".to_owned(), || {
- f.evaluate(
- args.clone()
- .into_iter()
- .map(move |a| {
- (
- a.clone().0,
- if *tailstrict {
- Val::Lazy(LazyVal::new_resolved(
- evaluate(context.clone(), &a.1).unwrap(),
+ Val::Func(f) => {
+ let body = #[inline(always)]
+ || {
+ f.evaluate(
+ args.clone()
+ .into_iter()
+ .map(
+ #[inline(always)]
+ move |a| {
+ Ok((
+ a.clone().0,
+ if *tailstrict {
+ Val::Lazy(LazyVal::new_resolved(evaluate(
+ context.clone(),
+ &a.1,
+ )?))
+ } else {
+ Val::Lazy(lazy_val!(
+ closure!(clone context, clone a, || evaluate(context.clone(), &a.clone().1))
+ ))
+ },
))
- } else {
- Val::Lazy(lazy_val!(
- closure!(clone context, clone a, || evaluate(context.clone(), &a.clone().1))
- ))
},
)
- })
- .collect(),
- )
- })?,
+ .collect::<Result<Vec<_>>>()?,
+ )
+ };
+ if *tailstrict {
+ body()?
+ } else {
+ push(locexpr, "function call".to_owned(), body)?
+ }
+ }
_ => panic!("{:?} is not a function", value),
}
}
@@ -578,9 +611,7 @@
)?
} else {
match cond_else {
- Some(v) => push(v.clone(), "if condition 'else' branch".to_owned(), || {
- evaluate(context, v)
- })?,
+ Some(v) => evaluate(context, v)?,
None => Val::Null,
}
}
crates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/lib.rs
+++ b/crates/jsonnet-evaluator/src/lib.rs
@@ -2,6 +2,7 @@
#![feature(type_alias_impl_trait)]
#![feature(debug_non_exhaustive)]
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
+#![feature(stmt_expr_attributes)]
mod ctx;
mod dynamic;
mod error;
@@ -51,6 +52,19 @@
}
}
+pub struct EvaluationSettings {
+ max_stack_frames: usize,
+ max_stack_trace_size: usize,
+}
+impl Default for EvaluationSettings {
+ fn default() -> Self {
+ EvaluationSettings {
+ max_stack_frames: 500,
+ max_stack_trace_size: 20,
+ }
+ }
+}
+
pub struct FileData(String, LocExpr, Option<Val>);
#[derive(Default)]
pub struct EvaluationStateInternals {
@@ -60,21 +74,31 @@
/// printing stacktraces
files: RefCell<HashMap<PathBuf, FileData>>,
globals: RefCell<HashMap<String, Val>>,
+
+ settings: EvaluationSettings,
}
thread_local! {
- pub static EVAL_STATE: RefCell<Option<EvaluationState>> = RefCell::new(None)
+ /// Contains state for currently executing file
+ /// Global state is fine there
+ pub(crate) static EVAL_STATE: RefCell<Option<EvaluationState>> = RefCell::new(None)
}
+#[inline(always)]
pub(crate) fn with_state<T>(f: impl FnOnce(&EvaluationState) -> T) -> T {
- EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap()))
+ EVAL_STATE.with(
+ #[inline(always)]
+ |s| f(s.borrow().as_ref().unwrap()),
+ )
}
pub(crate) fn create_error<T>(err: Error) -> Result<T> {
with_state(|s| s.error(err))
}
+#[inline(always)]
pub(crate) fn push<T>(e: LocExpr, comment: String, f: impl FnOnce() -> Result<T>) -> Result<T> {
with_state(|s| s.push(e, comment, f))
}
+/// Maintains stack trace and import resolution
#[derive(Default, Clone)]
pub struct EvaluationState(Rc<EvaluationStateInternals>);
impl EvaluationState {
@@ -189,10 +213,11 @@
Context::new().extend(new_bindings, None, None, None)
}
+ #[inline(always)]
pub fn push<T>(&self, e: LocExpr, comment: String, f: impl FnOnce() -> Result<T>) -> Result<T> {
{
let mut stack = self.0.stack.borrow_mut();
- if stack.len() > 500 {
+ if stack.len() > self.0.settings.max_stack_frames {
drop(stack);
return self.error(Error::StackOverflow);
} else {
@@ -209,7 +234,16 @@
}
}
pub fn stack_trace(&self) -> StackTrace {
- StackTrace(self.0.stack.borrow().iter().rev().cloned().collect())
+ StackTrace(
+ self.0
+ .stack
+ .borrow()
+ .iter()
+ .rev()
+ .take(self.0.settings.max_stack_trace_size)
+ .cloned()
+ .collect(),
+ )
}
pub fn error<T>(&self, err: Error) -> Result<T> {
Err(LocError(err, self.stack_trace()))
crates/jsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jsonnet-evaluator/src/val.rs
+++ b/crates/jsonnet-evaluator/src/val.rs
@@ -78,6 +78,8 @@
}
impl FuncDesc {
// TODO: Check for unset variables
+ /// This function is always inlined to make tailstrict work
+ #[inline(always)]
pub fn evaluate(&self, args: Vec<(Option<String>, Val)>) -> Result<Val> {
let mut new_bindings: HashMap<String, LazyBinding> = HashMap::new();
let future_ctx = Context::new_future();