use crate::bindings::attributes::AttributesBindingAdapter;
use crate::context::attributes::AttributesContext;
use crate::context::authentication::AuthenticationContext;
use crate::context::input::InputContext;
use crate::context::merged::MergedContext;
use crate::context::payload::PayloadContext;
use crate::context::reference::TransientContext;
use crate::context::vars::VarsContext;
use crate::value::IntoValue;
use crate::{AttributesBinding, AuthenticationBinding, PayloadBinding, Value};
use pel::expression::Expression as PelExpression;
use pel::runtime::value::Value as PelValue;
use pel::runtime::{Context, Evaluation, Runtime, RuntimeError};
use std::collections::HashMap;
use std::convert::TryFrom;
use thiserror::Error;
#[allow(unused)]
use crate::Script;
pub struct Evaluator<'a> {
evaluation: Option<PelExpression>,
result: Option<PelValue>,
error: Option<RuntimeError>,
input_context: &'a InputContext,
transient_context: TransientContext,
vars: HashMap<String, Value>,
}
impl<'a> Evaluator<'a> {
pub(crate) fn new(
evaluation: Option<PelExpression>,
result: Option<PelValue>,
context: &'a InputContext,
) -> Self {
Self {
evaluation,
result,
error: None,
input_context: context,
transient_context: TransientContext::default(),
vars: HashMap::new(),
}
}
pub fn bind_vars<K: IntoValue>(&mut self, name: &str, value: K) {
if !self.is_ready() {
self.vars.insert(name.to_string(), value.into_value());
if self
.input_context
.vars()
.iter()
.all(|item| self.vars.contains_key(item))
{
let vars = self.vars.drain().collect();
self.do_eval(&VarsContext::new(self.input_context, vars));
}
}
}
pub fn bind_payload(&mut self, binding: &dyn PayloadBinding) {
if !self.is_ready() {
self.do_eval(&PayloadContext::new(self.input_context, binding))
}
}
pub fn bind_attributes(&mut self, binding: &dyn AttributesBinding) {
if !self.is_ready() {
let adapter = AttributesBindingAdapter::new(binding);
self.do_eval(&AttributesContext::new(self.input_context, &adapter))
}
}
pub fn bind_authentication(&mut self, binding: &dyn AuthenticationBinding) {
if !self.is_ready() {
self.do_eval(&AuthenticationContext::new(self.input_context, binding))
}
}
fn do_eval(&mut self, bound_context: &'_ dyn Context) {
let local_context = MergedContext::new(self.input_context, &self.transient_context);
let merged_context = MergedContext::new(&local_context, bound_context);
if let Some(expr) = self.evaluation.take() {
match Runtime::new().eval_with_context(&expr, &merged_context) {
Ok(eval) => match eval {
Evaluation::Complete(_, v) => {
self.result = Some(v);
}
Evaluation::Partial(exp) => {
self.transient_context.detach_pending(&exp, bound_context);
self.evaluation = Some(exp);
}
},
Err(error) => {
self.error = Some(error);
}
}
}
}
pub fn is_ready(&self) -> bool {
self.result.is_some() || self.error.is_some()
}
pub fn eval(self) -> Result<Value, EvaluationError> {
if let Some(error) = self.error {
return Err(EvaluationError::Error(error));
}
self.result
.ok_or(EvaluationError::PartialEvaluation)
.and_then(|val| Value::try_from(&val))
}
}
#[derive(Error, Debug)]
pub enum EvaluationError {
#[error("Evaluation is incomplete")]
PartialEvaluation,
#[error("Unsupported DataType")]
TypeMismatch,
#[error("Error evaluating expression: {0}")]
Error(RuntimeError),
}