use crate::common::types::CelType;
use crate::common::value::{CowVal, Val};
use crate::magic::{Function, FunctionRegistry, IntoFunction};
use crate::objects::{TryIntoValue, Value};
use crate::parser::Expression;
use crate::{DeclarationError, Env, ExecutionError};
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::sync::Arc;
pub enum Context<'p, 'v> {
Root {
functions: FunctionRegistry,
variables: BTreeMap<String, Box<dyn Val + 'v>>,
resolver: Option<&'v dyn VariableResolver>,
env: Arc<Env>,
},
Child {
parent: &'p Context<'p, 'v>,
variables: BTreeMap<String, Box<dyn Val + 'v>>,
resolver: Option<&'v dyn VariableResolver>,
},
}
impl<'p, 'v> Context<'p, 'v> {
pub fn add_variable<S, V>(
&mut self,
name: S,
value: V,
) -> Result<(), <V as TryIntoValue>::Error>
where
S: Into<String>,
V: TryIntoValue,
{
let value = value.try_into_value()?;
let value: Box<dyn Val> = value.try_into().unwrap();
self.add_variable_as_val(name, value);
Ok(())
}
pub fn add_variable_from_value<S, V>(&mut self, name: S, value: V)
where
S: Into<String>,
V: Into<Value>,
{
let value = value.into();
let value: Box<dyn Val> = value.try_into().unwrap();
self.add_variable_as_val(name, value);
}
pub fn add_variable_as_val<S>(&mut self, name: S, value: Box<dyn Val + 'v>)
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: &'v dyn VariableResolver) {
match self {
Context::Root { resolver, .. } => {
*resolver = Some(r);
}
Context::Child { resolver, .. } => {
*resolver = Some(r);
}
}
}
pub fn get_variable<'b, S>(&'b self, name: S) -> Option<CowVal<'b, 'v>>
where
S: AsRef<str>,
{
let name = name.as_ref();
self.scopes().find_map(|scope| scope.own_variable(name))
}
fn scopes<'b>(&'b self) -> impl Iterator<Item = &'b Context<'p, 'v>> {
std::iter::successors(Some(self), |scope| match scope {
Context::Child { parent, .. } => Some(*parent),
Context::Root { .. } => None,
})
}
fn scopes_for<'b>(&'b self, name: &str) -> impl Iterator<Item = &'b Context<'p, 'v>> {
let absolute = name.starts_with('.');
self.scopes()
.filter(move |scope| !absolute || matches!(scope, Context::Root { .. }))
}
fn own_variable<'b>(&'b self, name: &str) -> Option<CowVal<'b, 'v>> {
let (variables, resolver) = match self {
Context::Child {
variables,
resolver,
..
}
| Context::Root {
variables,
resolver,
..
} => (variables, resolver),
};
resolver
.and_then(|r| r.resolve(name))
.or_else(|| variables.get(name).map(|v| CowVal::Borrowed(v.as_ref())))
}
pub(crate) fn env(&self) -> &Env {
match self {
Context::Root { env, .. } => env.as_ref(),
Context::Child { parent, .. } => parent.env(),
}
}
pub(crate) fn has_function_namespace(&self, namespace: &str) -> bool {
match self {
Context::Root { functions, env, .. } => {
env.has_namespace(namespace) || functions.has_namespace(namespace)
}
Context::Child { parent, .. } => parent.has_function_namespace(namespace),
}
}
pub(crate) fn resolve_ident(&self, name: &str) -> Option<CowVal<'_, 'v>> {
self.scopes_for(name)
.find_map(|scope| scope.resolve_own(name, true))
}
pub(crate) fn resolve_qualified<'b, 's>(
&'b self,
segments: &'s [&'b str],
) -> Result<(CowVal<'b, 'v>, &'s [&'b str]), ExecutionError> {
let name = segments.join(".");
for scope in self.scopes_for(&name) {
let mut len = name.len();
for prefix in (1..=segments.len()).rev() {
let whole = prefix == segments.len();
if let Some(value) = scope.resolve_own(&name[..len], whole) {
return Ok((value, &segments[prefix..]));
}
len = len.saturating_sub(segments[prefix - 1].len() + 1);
}
}
Err(ExecutionError::UndeclaredReference(Arc::new(
segments[0].to_owned(),
)))
}
fn resolve_own(&self, name: &str, may_be_type: bool) -> Option<CowVal<'_, 'v>> {
let env = self.env();
env.container().candidates(name).find_map(|candidate| {
self.own_variable(&candidate).or_else(|| {
if !may_be_type || !matches!(self, Context::Root { .. }) {
return None;
}
let t = env.types().find_type(&candidate)?;
Some(CowVal::owned(CelType::from(t)))
})
})
}
pub(crate) fn find_struct(&self, name: &str) -> Option<&dyn crate::StructType> {
let types = self.env().types();
self.env()
.container()
.candidates(name)
.find_map(|candidate| types.find_struct(&candidate))
}
fn has_function(&self, name: &str) -> bool {
self.env().has_overload(name) || self.get_function(name).is_some()
}
pub(crate) fn resolve_function<'n>(&self, name: &'n str) -> Cow<'n, str> {
let mut candidates = self.env().container().candidates(name).peekable();
while let Some(candidate) = candidates.next() {
if candidates.peek().is_none() || self.has_function(&candidate) {
return candidate;
}
}
Cow::Borrowed(name)
}
pub(crate) fn resolve_qualified_function(
&self,
target: &Expression,
func_name: &str,
) -> Option<String> {
let container = self.env().container();
let root = target.expr.qualified_name_root()?;
if !container
.candidates(root)
.any(|c| self.has_function_namespace(c.split('.').next().unwrap_or_default()))
{
return None;
}
let segments = target.expr.qualified_name_segments()?;
let name = format!("{}.{func_name}", segments.join("."));
let found = container
.candidates(&name)
.find(|candidate| self.has_function(candidate))
.map(Cow::into_owned);
found
}
#[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,
) -> Result<(), DeclarationError>
where
F: IntoFunction<T> + 'static + Send + Sync,
{
if let Context::Root { functions, env, .. } = self {
if env.has_overload(name) || env.has_member_overload(name) {
return Err(DeclarationError::overload_conflict(name));
}
functions.add(name, value);
};
Ok(())
}
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<'b>(&'b self) -> Context<'b, 'v> {
Context::Child {
parent: self,
variables: Default::default(),
resolver: None,
}
}
pub fn empty() -> Self {
Context::Root {
env: Arc::new(Env::default()),
variables: Default::default(),
functions: Default::default(),
resolver: None,
}
}
pub fn with_env(env: Arc<Env>) -> Self {
Context::Root {
env,
variables: Default::default(),
functions: Default::default(),
resolver: None,
}
}
}
impl Default for Context<'_, '_> {
fn default() -> Self {
Context::Root {
env: Arc::new(Env::stdlib()),
variables: Default::default(),
functions: Default::default(),
resolver: None,
}
}
}
pub trait VariableResolver: Send + Sync {
fn resolve<'b>(&'b self, variable: &str) -> Option<CowVal<'b, 'b>>;
}
impl<T: VariableResolver> VariableResolver for Box<T> {
fn resolve<'b>(&'b self, variable: &str) -> Option<CowVal<'b, 'b>> {
(**self).resolve(variable)
}
}
impl<T: VariableResolver> VariableResolver for Arc<T> {
fn resolve<'b>(&'b self, variable: &str) -> Option<CowVal<'b, 'b>> {
(**self).resolve(variable)
}
}
impl<T: VariableResolver> VariableResolver for &T {
fn resolve<'b>(&'b self, variable: &str) -> Option<CowVal<'b, 'b>> {
(**self).resolve(variable)
}
}
#[cfg(test)]
mod test {
use super::{Context, VariableResolver};
use crate::common::types::CelString;
use crate::common::value::CowVal;
fn assert_send<T: Send>() {}
#[test]
fn test_context_is_send() {
assert_send::<super::Context>();
}
#[test]
fn test_variable_resolver_returns_val_directly() {
use crate::common::traits::Indexer;
use crate::common::types::{CelInt, Type, DYN_TYPE};
use crate::common::value::{CowVal, Val};
use crate::{Context, ExecutionError, Program, Value};
#[derive(Debug)]
struct Lazy;
impl Val for Lazy {
fn get_type(&self) -> &Type {
&DYN_TYPE
}
fn cel_type() -> &'static Type {
&DYN_TYPE
}
fn as_indexer<'b, 'v>(&'b self) -> Option<&'b (dyn Indexer + 'v)>
where
Self: 'v,
{
Some(self)
}
fn clone_as_boxed<'v>(&self) -> Box<dyn Val + 'v> {
Box::new(Lazy)
}
}
impl Indexer for Lazy {
fn get<'b, 'v>(&'b self, _idx: &dyn Val) -> Result<CowVal<'b, 'v>, ExecutionError>
where
Self: 'v,
{
Ok(CowVal::owned(CelInt::from(42)))
}
fn steal<'v>(
self: Box<Self>,
_idx: &dyn Val,
) -> Result<Box<dyn Val + 'v>, ExecutionError>
where
Self: 'v,
{
Ok(Box::new(CelInt::from(42)))
}
}
struct LazyResolver;
impl super::VariableResolver for LazyResolver {
fn resolve<'b>(&'b self, variable: &str) -> Option<CowVal<'b, 'b>> {
(variable == "input").then(|| CowVal::owned(Lazy))
}
}
let mut ctx = Context::default();
ctx.set_variable_resolver(&LazyResolver);
let program = Program::compile("input.anything").unwrap();
assert_eq!(program.execute(&ctx), Ok(Value::Int(42)));
}
#[test]
fn test_variable_resolver_can_borrow() {
use crate::common::types::{Type, DYN_TYPE};
use crate::common::value::{CowVal, Val};
use crate::{Context, Program};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[derive(Debug)]
struct CountedVal(Arc<AtomicUsize>);
impl Val for CountedVal {
fn get_type(&self) -> &Type {
&DYN_TYPE
}
fn cel_type() -> &'static Type {
&DYN_TYPE
}
fn clone_as_boxed<'v>(&self) -> Box<dyn Val + 'v> {
self.0.fetch_add(1, Ordering::SeqCst);
Box::new(CountedVal(self.0.clone()))
}
}
struct BorrowResolver(Box<dyn Val>);
impl super::VariableResolver for BorrowResolver {
fn resolve<'b>(&'b self, variable: &str) -> Option<CowVal<'b, 'b>> {
(variable == "counted").then(|| CowVal::Borrowed(self.0.as_ref()))
}
}
let clones = Arc::new(AtomicUsize::new(0));
let resolver = BorrowResolver(Box::new(CountedVal(clones.clone())));
let mut ctx = Context::default();
ctx.set_variable_resolver(&resolver);
let program = Program::compile("counted == counted").unwrap();
program.execute(&ctx).unwrap();
assert_eq!(clones.load(Ordering::SeqCst), 0);
}
struct Borrowing<'a>(&'a str);
impl VariableResolver for Borrowing<'_> {
fn resolve<'b>(&'b self, variable: &str) -> Option<CowVal<'b, 'b>> {
(variable == "s").then(|| CowVal::owned(CelString::from(self.0)))
}
}
#[test]
fn resolver_value_borrows_through_a_full_evaluation() {
use crate::parser::Parser;
use crate::Value;
let owned = String::from("cel-rust");
let resolver = Borrowing(owned.as_str());
let mut ctx = Context::default();
ctx.set_variable_resolver(&resolver);
for expr in [
"s",
"s == 'cel-rust' ? s : 'other'",
"string(s)",
"dyn(s)",
"optional.of(s).value()",
"optional.of(s).orValue('other')",
"[s][0]",
"{'k': s}.k",
"{'k': s}['k']",
"[s].map(x, x)[0]",
"[1].map(x, s)[0]",
] {
let ast = Parser::default()
.enable_optional_syntax(true)
.parse(expr)
.unwrap();
let v = Value::resolve_val(&ast, &ctx).unwrap();
let s = v.downcast_ref::<CelString>().unwrap();
assert!(
std::ptr::eq(s.inner(), owned.as_str()),
"`{expr}` copied the string"
);
}
}
#[test]
fn resolver_value_borrows_through_the_context_and_a_child_scope() {
let owned = String::from("cel-rust");
let resolver = Borrowing(owned.as_str());
let mut ctx = Context::default();
ctx.set_variable_resolver(&resolver);
let inner = ctx.new_inner_scope();
let v = inner.get_variable("s").unwrap();
let s = v.downcast_ref::<CelString>().unwrap();
assert!(std::ptr::eq(s.inner(), owned.as_str()));
let escaped: Box<dyn crate::common::value::Val + '_> = v.into_owned();
drop(inner);
let s = escaped.downcast_ref::<CelString>().unwrap();
assert!(std::ptr::eq(s.inner(), owned.as_str()));
}
#[test]
fn add_function_rejects_a_name_declared_as_an_overload() {
use crate::{Context, DeclarationError};
let mut context = Context::default();
for name in ["int", "startsWith", "size", "optional.of"] {
assert_eq!(
context.add_function(name, |a: i64| a),
Err(DeclarationError::overload_conflict(name)),
"{name}"
);
}
}
#[test]
fn add_function_accepts_a_name_no_overload_uses() {
use crate::{Context, Program};
let mut context = Context::default();
assert_eq!(context.add_function("add", |a: i64, b: i64| a + b), Ok(()));
let program = Program::compile("add(2, 3)").unwrap();
assert_eq!(program.execute(&context), Ok(5.into()));
}
#[test]
fn a_rejected_function_is_not_registered() {
use crate::{Context, ExecutionError, Program};
let mut context = Context::default();
assert!(context.add_function("size", |_: i64| 42_i64).is_err());
let program = Program::compile("size(1)").unwrap();
assert!(matches!(
program.execute(&context),
Err(ExecutionError::NoSuchOverload(_))
));
let program = Program::compile("size('abc')").unwrap();
assert_eq!(program.execute(&context), Ok(3.into()));
}
#[test]
fn add_function_only_conflicts_with_the_overloads_of_its_env() {
use crate::common::types::INT_TYPE;
use crate::common::value::CowVal;
use crate::{Context, DeclarationError, Env, ExecutionError};
use std::sync::Arc;
assert_eq!(Context::empty().add_function("size", |a: i64| a), Ok(()),);
fn custom<'b, 'v>(args: Vec<CowVal<'b, 'v>>) -> Result<CowVal<'b, 'v>, ExecutionError> {
Ok(args.into_iter().next().unwrap())
}
let mut env = Env::default();
env.add_overload("custom", "custom_int", vec![INT_TYPE], custom)
.unwrap();
let mut context = Context::with_env(Arc::new(env));
assert_eq!(
context.add_function("custom", |a: i64| a),
Err(DeclarationError::overload_conflict("custom")),
);
assert_eq!(context.add_function("other", |a: i64| a), Ok(()));
}
#[test]
fn add_function_on_a_child_is_a_silent_noop_ignores_dups() {
use crate::Context;
let context = Context::default();
let mut child = context.new_inner_scope();
assert_eq!(child.add_function("size", |a: i64| a), Ok(()),);
}
#[test]
fn a_qualified_function_declares_its_namespace() {
let mut context = Context::default();
context.add_function("a.b.f", |i: i64| i).unwrap();
context.add_function("g", |i: i64| i).unwrap();
assert!(context.has_function_namespace("a"));
assert!(!context.has_function_namespace("g"));
assert!(context.has_function_namespace("optional"), "from the Env");
assert!(context.new_inner_scope().has_function_namespace("a"));
}
#[test]
fn a_type_name_is_not_a_variable() {
let context = Context::default();
assert!(context.get_variable("int").is_none());
assert!(context.new_inner_scope().get_variable("int").is_none());
}
}