use crate::common::value::Val;
use crate::magic::{Function, FunctionRegistry, IntoFunction};
use crate::objects::{TryIntoValue, Value};
use crate::parser::Expression;
use crate::{functions, ExecutionError};
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::sync::Arc;
pub enum Context<'a> {
Root {
functions: FunctionRegistry,
variables: BTreeMap<String, Box<dyn Val>>,
resolver: Option<&'a dyn VariableResolver>,
},
Child {
parent: &'a Context<'a>,
variables: BTreeMap<String, Box<dyn Val>>,
resolver: Option<&'a dyn VariableResolver>,
},
}
impl<'a> Context<'a> {
pub fn add_variable<S, V>(
&mut self,
name: S,
value: V,
) -> Result<(), <V as TryIntoValue>::Error>
where
S: Into<String>,
V: TryIntoValue,
{
match self {
Context::Root { variables, .. } => {
let value = value.try_into_value()?;
let value: Box<dyn Val> = value.try_into().unwrap();
variables.insert(name.into(), value);
}
Context::Child { variables, .. } => {
let value = value.try_into_value()?;
let value: Box<dyn Val> = value.try_into().unwrap();
variables.insert(name.into(), value);
}
}
Ok(())
}
pub fn add_variable_from_value<S, V>(&mut self, name: S, value: V)
where
S: Into<String>,
V: Into<Value>,
{
match self {
Context::Root { variables, .. } => {
let value = value.into();
let value: Box<dyn Val> = value.try_into().unwrap();
variables.insert(name.into(), value);
}
Context::Child { variables, .. } => {
let value = value.into();
let value: Box<dyn Val> = value.try_into().unwrap();
variables.insert(name.into(), value);
}
}
}
pub(crate) fn add_variable_as_val<S>(&mut self, name: S, value: Box<dyn Val>)
where
S: Into<String>,
{
match self {
Context::Root { variables, .. } => {
variables.insert(name.into(), value);
}
Context::Child { variables, .. } => {
variables.insert(name.into(), value);
}
}
}
pub fn set_variable_resolver(&mut self, r: &'a dyn VariableResolver) {
match self {
Context::Root { resolver, .. } => {
*resolver = Some(r);
}
Context::Child { resolver, .. } => {
*resolver = Some(r);
}
}
}
pub fn get_variable<S>(&'a self, name: S) -> Option<Cow<'a, dyn Val>>
where
S: AsRef<str>,
{
let name = name.as_ref();
match self {
Context::Child {
variables,
parent,
resolver,
} => resolver
.and_then(|r| {
r.resolve(name)
.map(|v| Cow::<dyn Val>::Owned(v.try_into().unwrap()))
})
.or_else(|| {
variables
.get(name)
.map(|b| Cow::<dyn Val>::Borrowed(b.as_ref()))
.or_else(|| parent.get_variable(name))
}),
Context::Root {
variables,
resolver,
..
} => resolver
.and_then(|r| {
r.resolve(name)
.map(|v| Cow::<dyn Val>::Owned(v.try_into().unwrap()))
})
.or_else(|| {
variables
.get(name)
.map(|v| Cow::<dyn Val>::Borrowed(v.as_ref()))
}),
}
}
#[allow(dead_code)]
pub(crate) fn get_function(&self, name: &str) -> Option<&Function> {
match self {
Context::Root { functions, .. } => functions.get(name),
Context::Child { parent, .. } => parent.get_function(name),
}
}
pub fn add_function<T: 'static, F>(&mut self, name: &str, value: F)
where
F: IntoFunction<T> + 'static + Send + Sync,
{
if let Context::Root { functions, .. } = self {
functions.add(name, value);
};
}
pub fn resolve(&self, expr: &Expression) -> Result<Value, ExecutionError> {
Value::resolve(expr, self)
}
pub fn resolve_all(&self, exprs: &[Expression]) -> Result<Value, ExecutionError> {
Value::resolve_all(exprs, self)
}
pub fn new_inner_scope(&self) -> Context<'_> {
Context::Child {
parent: self,
variables: Default::default(),
resolver: None,
}
}
pub fn empty() -> Self {
Context::Root {
variables: Default::default(),
functions: Default::default(),
resolver: None,
}
}
}
impl Default for Context<'_> {
fn default() -> Self {
let mut ctx = Context::Root {
variables: Default::default(),
functions: Default::default(),
resolver: None,
};
ctx.add_function("contains", functions::contains);
ctx.add_function("size", functions::size);
ctx.add_function("max", functions::max);
ctx.add_function("min", functions::min);
ctx.add_function("startsWith", functions::starts_with);
ctx.add_function("endsWith", functions::ends_with);
ctx.add_function("string", functions::string);
ctx.add_function("bytes", functions::bytes);
ctx.add_function("double", functions::double);
ctx.add_function("int", functions::int);
ctx.add_function("uint", functions::uint);
ctx.add_function("optional.none", functions::optional_none);
ctx.add_function("optional.of", functions::optional_of);
ctx.add_function(
"optional.ofNonZeroValue",
functions::optional_of_non_zero_value,
);
ctx.add_function("value", functions::optional_value);
ctx.add_function("hasValue", functions::optional_has_value);
ctx.add_function("or", functions::optional_or_optional);
ctx.add_function("orValue", functions::optional_or_value);
#[cfg(feature = "regex")]
ctx.add_function("matches", functions::matches);
#[cfg(feature = "chrono")]
{
ctx.add_function("duration", functions::duration);
ctx.add_function("timestamp", functions::timestamp);
ctx.add_function("getFullYear", functions::time::timestamp_year);
ctx.add_function("getMonth", functions::time::timestamp_month);
ctx.add_function("getDayOfYear", functions::time::timestamp_year_day);
ctx.add_function("getDayOfMonth", functions::time::timestamp_month_day);
ctx.add_function("getDate", functions::time::timestamp_date);
ctx.add_function("getDayOfWeek", functions::time::timestamp_weekday);
ctx.add_function("getHours", functions::time::get_hours);
ctx.add_function("getMinutes", functions::time::get_minutes);
ctx.add_function("getSeconds", functions::time::get_seconds);
ctx.add_function("getMilliseconds", functions::time::get_milliseconds);
}
ctx
}
}
pub trait VariableResolver: Send + Sync {
fn resolve(&self, variable: &str) -> Option<Value>;
}
impl<T: VariableResolver> VariableResolver for Box<T> {
fn resolve(&self, variable: &str) -> Option<Value> {
(**self).resolve(variable)
}
}
impl<T: VariableResolver> VariableResolver for Arc<T> {
fn resolve(&self, variable: &str) -> Option<Value> {
(**self).resolve(variable)
}
}
impl<T: VariableResolver> VariableResolver for &T {
fn resolve(&self, variable: &str) -> Option<Value> {
(**self).resolve(variable)
}
}
#[cfg(test)]
mod test {
fn assert_send<T: Send>() {}
#[test]
fn test_context_is_send() {
assert_send::<super::Context>();
}
}