use super::function;
use alloc::{
boxed::Box,
collections::BTreeMap,
string::{String, ToString},
};
use serde_json::Value;
#[derive(Copy, Clone)]
pub struct EvaluateContext<'engine, 'json, T> {
engine: &'engine Engine<T>,
value: &'json Value,
previous_value: Option<&'json Value>,
state: T,
}
impl<'engine, 'json> EvaluateContext<'engine, 'json, ()> {
pub fn new(engine: &'engine Engine<()>, value: &'json Value) -> Self {
EvaluateContext {
engine,
value,
previous_value: None,
state: (),
}
}
}
impl<'engine, 'json, T> EvaluateContext<'engine, 'json, T> {
pub fn new_with_state(engine: &'engine Engine<T>, value: &'json Value, state: T) -> Self {
EvaluateContext {
value,
previous_value: None,
engine,
state,
}
}
pub fn with_previous(self, previous_value: &'json Value) -> Self {
self.set_previous(Some(previous_value))
}
pub fn set_previous(mut self, previous_value: Option<&'json Value>) -> Self {
self.previous_value = previous_value;
self
}
pub fn value(&self) -> &'json Value {
self.value
}
pub fn previous_value(&self) -> Option<&'json Value> {
self.previous_value
}
pub fn engine(&self) -> &'engine Engine<T> {
self.engine
}
pub fn state(&self) -> &T {
&self.state
}
pub fn state_mut(&mut self) -> &mut T {
&mut self.state
}
pub(crate) fn function_context(&mut self) -> function::FunctionContext<'_, T> {
function::FunctionContext::new(&mut self.state)
}
}
pub struct Engine<T> {
functions: BTreeMap<String, Box<dyn function::Function<T>>>,
}
impl<T> Engine<T> {
pub fn empty() -> Self {
Self {
functions: BTreeMap::default(),
}
}
pub fn function(&self, name: &str) -> Option<&dyn function::Function<T>> {
self.functions.get(name).map(|bx| bx.as_ref())
}
pub fn set_function(&mut self, name: impl ToString, function: Box<dyn function::Function<T>>) {
self.functions.insert(name.to_string(), function);
}
pub fn with_function(
mut self,
name: impl ToString,
function: Box<dyn function::Function<T>>,
) -> Self {
self.set_function(name, function);
self
}
}
impl<T> Default for Engine<T> {
fn default() -> Self {
let mut engine = Engine::empty();
engine.set_function("SET", Box::new(function::SetFunction));
engine.set_function("UNION", Box::new(function::UnionFunction));
engine.set_function("DIFFERENCE", Box::new(function::DifferenceFunction));
engine.set_function("INTERSECTION", Box::new(function::IntersectionFunction));
engine.set_function("LENGTH", Box::new(function::LengthFunction));
engine.set_function("LIST", Box::new(function::ListFunction));
engine.set_function("BOOL", Box::new(function::BoolFunction));
engine.set_function("CHAR", Box::new(function::CharFunction));
engine.set_function("CHARF", Box::new(function::CharfFunction));
engine.set_function("TIME", Box::new(function::TimeFunction));
engine.set_function("DATE", Box::new(function::DateFunction));
engine.set_function("INT", Box::new(function::IntFunction));
engine.set_function("FLOAT", Box::new(function::FloatFunction));
engine.set_function("SUBSTR", Box::new(function::SubstrFunction));
engine.set_function("STRLEN", Box::new(function::StrlenFunction));
engine.set_function("LOWER", Box::new(function::LowerFunction));
engine.set_function("UPPER", Box::new(function::UpperFunction));
engine.set_function("YEAR", Box::new(function::YearFunction));
engine.set_function("MONTH", Box::new(function::MonthFunction));
engine.set_function("DAY", Box::new(function::DayFunction));
engine.set_function("WEEKDAY", Box::new(function::WeekdayFunction));
engine.set_function("TYPEOF", Box::new(function::TypeofFunction));
#[cfg(feature = "match_function")]
engine.set_function("MATCH", Box::new(function::MatchFunction));
#[cfg(feature = "std")]
engine.set_function("NOW", Box::new(function::NowFunction));
#[cfg(feature = "std")]
engine.set_function("TODAY", Box::new(function::TodayFunction));
engine
}
}
impl<T> core::fmt::Debug for Engine<T>
where
T: core::fmt::Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
struct FunctionList<'a, T>(&'a BTreeMap<String, Box<dyn function::Function<T>>>);
impl<'a, T> core::fmt::Debug for FunctionList<'a, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut list = f.debug_list();
for name in self.0.keys() {
list.entry(name);
}
list.finish()
}
}
f.debug_struct("Engine")
.field("functions", &FunctionList(&self.functions))
.finish()
}
}
#[cfg(test)]
mod tests {
use crate::*;
use alloc::borrow::Cow;
use serde_json::json;
#[test]
fn test_with_state() {
struct Inc;
impl function::Function<u32> for Inc {
fn evaluate<'json>(
&self,
mut context: function::FunctionContext<'_, u32>,
_input: Vec<Cow<'json, serde_json::Value>>,
) -> Result<Cow<'json, serde_json::Value>, function::FunctionError> {
let value = *context.state();
*context.state_mut() += 1;
Ok(Cow::Owned(serde_json::Value::Number(
serde_json::Number::from(value),
)))
}
}
let engine = Engine::empty().with_function("INC", Box::new(Inc));
let expression = "INC() + INC() + INC() + INC()"
.parse::<Expression>()
.unwrap();
let value = json!({});
let context = EvaluateContext::new_with_state(&engine, &value, 1);
let value = expression.apply(context).unwrap().into_owned();
assert_eq!(value.as_u64().unwrap(), 1 + 2 + 3 + 4);
let context = EvaluateContext::new_with_state(&engine, &value, 10);
let value = expression.apply(context).unwrap().into_owned();
assert_eq!(value.as_u64().unwrap(), 10 + 11 + 12 + 13);
}
}