use crate::common::{
decls::FunctionDecl,
functions::Function,
types::{self, Type},
value::CowVal,
};
use crate::container::Container;
use crate::parser::{Macro, Macros, Parser};
use crate::registry::{TypeDecl, TypeRegistry};
use crate::{common::types::CelStruct, common::value::Val, ExecutionError, StructType};
use crate::{DeclarationError, ParseErrors, Program};
use std::collections::{
btree_map::Entry::{Occupied, Vacant},
BTreeMap, BTreeSet,
};
use std::sync::Arc;
pub struct Env {
functions: BTreeMap<String, FunctionDecl>,
namespaces: BTreeSet<String>,
types: TypeRegistry,
macros: Arc<Macros>,
error_on_duplicate_map_keys: bool,
optional: bool,
container: Container,
}
impl Default for Env {
fn default() -> Self {
Env {
functions: BTreeMap::new(),
namespaces: BTreeSet::new(),
types: TypeRegistry::default(),
macros: Arc::default(),
error_on_duplicate_map_keys: true,
optional: true,
container: Container::default(),
}
}
}
impl Env {
pub fn stdlib() -> Env {
Env {
macros: Macros::standard(),
..Default::default()
}
.with_stdlib()
}
pub fn with_stdlib(mut self) -> Self {
types::bool::stdlib(&mut self);
types::bytes::stdlib(&mut self);
types::double::stdlib(&mut self);
types::r#dyn::stdlib(&mut self);
types::int::stdlib(&mut self);
types::list::stdlib(&mut self);
types::map::stdlib(&mut self);
types::null::stdlib(&mut self);
if self.optional {
types::optional::stdlib(&mut self);
}
types::string::stdlib(&mut self);
types::type_val::stdlib(&mut self);
types::uint::stdlib(&mut self);
#[cfg(feature = "chrono")]
{
types::duration::stdlib(&mut self);
types::timestamp::stdlib(&mut self);
}
self
}
pub fn with_optional_support(mut self, optional: bool) -> Self {
self.optional = optional;
self
}
pub fn parser(&self) -> Parser {
Parser::new()
.enable_optional_syntax(self.optional)
.with_macros(Arc::clone(&self.macros))
}
pub fn compile(&self, source: &str) -> Result<Program, ParseErrors> {
Program::parse_with(self.parser(), source)
}
pub fn add_macro(&mut self, m: Macro) -> Result<(), DeclarationError> {
Arc::make_mut(&mut self.macros).add(m)
}
pub fn add_overload(
&mut self,
name: &str,
id: &str,
args: Vec<types::Type>,
op: Function,
) -> Result<(), DeclarationError> {
match self.functions.entry(name.to_owned()) {
Vacant(vacant_entry) => {
let mut value = FunctionDecl::new(name);
value.add_overload(id.to_string(), false, args, op)?;
vacant_entry.insert(value);
if let Some((namespace, _)) = name.split_once('.') {
self.namespaces.insert(namespace.to_owned());
}
Ok(())
}
Occupied(occupied_entry) => {
occupied_entry
.into_mut()
.add_overload(id.to_string(), false, args, op)
}
}
}
pub(crate) fn has_namespace(&self, namespace: &str) -> bool {
self.namespaces.contains(namespace)
}
pub fn find_overload(&self, name: &str, args: &[CowVal<'_, '_>]) -> Option<Function> {
match self.functions.get(name) {
None => None,
Some(fn_decl) => fn_decl.find_overload(false, args),
}
}
pub(crate) fn has_overload(&self, name: &str) -> bool {
self.functions
.get(name)
.is_some_and(|function| function.has_overload(false))
}
pub fn add_member_overload(
&mut self,
name: &str,
id: &str,
target: Type,
args: Vec<types::Type>,
op: Function,
) -> Result<(), DeclarationError> {
let mut args = args;
args.insert(0, target);
match self.functions.entry(name.to_owned()) {
Vacant(vacant_entry) => {
let mut value = FunctionDecl::new(name);
value.add_overload(id.to_string(), true, args, op)?;
vacant_entry.insert(value);
Ok(())
}
Occupied(occupied_entry) => {
occupied_entry
.into_mut()
.add_overload(id.to_string(), true, args, op)
}
}
}
pub(crate) fn find_member_overload(
&self,
name: &str,
args: &[CowVal<'_, '_>],
) -> Option<Function> {
match self.functions.get(name) {
None => None,
Some(fn_decl) => fn_decl.find_overload(true, args),
}
}
pub(crate) fn has_member_overload(&self, name: &str) -> bool {
self.functions
.get(name)
.is_some_and(|function| function.has_overload(true))
}
pub fn add_type(&mut self, t: impl Into<TypeDecl>) -> Result<(), DeclarationError> {
self.types.register(t)
}
pub fn set_container(&mut self, name: &str) -> Result<(), DeclarationError> {
self.container = Container::new(name)?;
Ok(())
}
pub fn container_name(&self) -> &str {
self.container.name()
}
pub(crate) fn container(&self) -> &Container {
&self.container
}
pub fn types(&self) -> &TypeRegistry {
&self.types
}
pub fn add_extension(
&mut self,
extension: impl FnOnce(&mut Env) -> Result<(), DeclarationError>,
) -> Result<(), DeclarationError> {
extension(self)
}
pub fn set_error_on_duplicate_map_keys(&mut self, value: bool) {
self.error_on_duplicate_map_keys = value;
}
pub(crate) fn error_on_duplicate_map_keys(&self) -> bool {
self.error_on_duplicate_map_keys
}
}
pub struct StructDef {
r#type: Type,
fields: BTreeMap<String, Type>,
defaults: BTreeMap<String, Box<dyn Val>>,
}
impl StructDef {
pub fn new(name: String) -> Self {
Self {
r#type: Type::new_struct(name),
fields: Default::default(),
defaults: Default::default(),
}
}
pub fn add_field(self, field: String, t: Type) -> Self {
self.insert_field(field, t, None)
}
pub fn add_field_with_default(self, field: String, default: Box<dyn Val>) -> Self {
self.insert_field(field, default.get_type().to_owned(), Some(default))
}
fn insert_field(self, field: String, t: Type, default: Option<Box<dyn Val>>) -> Self {
let mut def = self;
def.fields.insert(field.clone(), t);
if let Some(default) = default {
def.defaults.insert(field, default);
}
def
}
fn new_struct<'b, 'v>(
&self,
fields: BTreeMap<String, CowVal<'b, 'v>>,
) -> Result<CelStruct<'v>, ExecutionError> {
let name = self.r#type.name();
let mut s = CelStruct::new(name.to_owned());
let mut fields = fields;
for (field, default) in &self.defaults {
if let Some(value) = fields.remove(field) {
s.add_field_value(field.clone(), value);
} else {
s.add_field_value(field.clone(), CowVal::Owned(default.clone_as_boxed()));
}
}
for (field, value) in fields {
match self.fields.get(&field) {
Some(t) => {
if t != value.get_type() {
return Err(ExecutionError::UnexpectedType {
got: value.get_type().name().to_owned(),
want: format!("{} for field {field} in {name}", t.name()),
});
}
s.add_field_value(field, value);
}
None => {
return Err(ExecutionError::NoSuchKey(std::sync::Arc::new(format!(
"field `{field}` on struct `{name}`"
))))
}
}
}
Ok(s)
}
}
impl StructType for StructDef {
fn get_type(&self) -> &Type {
&self.r#type
}
fn new_value<'b, 'v>(
&self,
fields: BTreeMap<String, CowVal<'b, 'v>>,
) -> Result<Box<dyn Val + 'v>, ExecutionError> {
Ok(Box::new(self.new_struct(fields)?))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::value::Val;
use std::sync::Arc;
#[test]
fn test_env_default() {
let _: Arc<dyn Send + Sync> = Arc::new(Env::default());
}
#[test]
fn an_empty_container_name_clears_the_container() {
use crate::{Context, Program, Value};
let program = Program::compile("y").unwrap();
let run = |env: &Arc<Env>| {
let mut context = Context::with_env(env.clone());
context.add_variable_from_value("x.y", true);
program.execute(&context)
};
let mut env = Arc::new(Env::stdlib());
assert_eq!(env.container_name(), "");
let env_mut = Arc::get_mut(&mut env).unwrap();
env_mut.set_container("x").unwrap();
assert!(env_mut.set_container(".x").is_err());
assert_eq!(env.container_name(), "x", "a failed call changes nothing");
assert_eq!(run(&env), Ok(Value::Bool(true)));
Arc::get_mut(&mut env).unwrap().set_container("").unwrap();
assert_eq!(env.container_name(), "");
assert!(run(&env).is_err(), "`y` no longer means `x.y`");
}
#[test]
fn its_parser_expands_the_macros_of_the_env_only() {
use crate::common::ast::Expr;
let source = "[1].exists(x, x > 0)";
let expanded = Env::stdlib().parser().parse(source).unwrap();
assert!(
matches!(expanded.expr, Expr::Comprehension(_)),
"{expanded:?}"
);
let as_written = Env::default().parser().parse(source).unwrap();
assert!(matches!(as_written.expr, Expr::Call(_)), "{as_written:?}");
}
fn first_arg(
_: &mut crate::parser::MacroExprHelper<'_>,
_: &mut Option<crate::IdedExpr>,
args: &mut Vec<crate::IdedExpr>,
) -> Result<Option<crate::IdedExpr>, crate::ParseError> {
Ok(Some(args.remove(0)))
}
#[test]
fn a_macro_expands_only_the_calls_of_its_style_and_argument_count() {
use crate::common::ast::Expr;
let mut env = Env::default();
env.add_macro(Macro::receiver("f", 1, first_arg)).unwrap();
let expanded = env.parser().parse("m.f(1)").unwrap();
assert!(matches!(expanded.expr, Expr::Literal(_)), "{expanded:?}");
for source in ["m.f()", "m.f(1, 2)", "f(1)"] {
let as_written = env.parser().parse(source).unwrap();
assert!(
matches!(as_written.expr, Expr::Call(_)),
"{source}: {as_written:?}"
);
}
}
#[test]
fn a_macro_for_the_calls_of_another_is_a_duplicate() {
let mut env = Env::stdlib();
assert_eq!(
env.add_macro(Macro::receiver("exists", 2, first_arg)),
Err(DeclarationError::duplicate_macro("exists")),
"the standard `exists` expands those"
);
assert_eq!(env.add_macro(Macro::global("exists", 2, first_arg)), Ok(()));
assert_eq!(
env.add_macro(Macro::receiver("exists", 3, first_arg)),
Ok(())
);
assert_eq!(
env.add_macro(Macro::receiver("exists", 3, first_arg)),
Err(DeclarationError::duplicate_macro("exists"))
);
assert_eq!(
env.add_macro(Macro::receiver_var_arg("exists", first_arg)),
Ok(())
);
assert_eq!(
env.add_macro(Macro::receiver_var_arg("exists", first_arg)),
Err(DeclarationError::duplicate_macro("exists"))
);
assert_eq!(
env.add_macro(Macro::global_var_arg("exists", first_arg)),
Ok(())
);
}
fn takes_then_declines(
_: &mut crate::parser::MacroExprHelper<'_>,
_: &mut Option<crate::IdedExpr>,
args: &mut Vec<crate::IdedExpr>,
) -> Result<Option<crate::IdedExpr>, crate::ParseError> {
match args[0].expr {
crate::common::ast::Expr::Literal(_) => drop(args.pop()),
_ => drop(std::mem::take(&mut args[0])),
}
Ok(None)
}
#[test]
fn declining_a_call_after_taking_from_it_is_an_error() {
let mut env = Env::stdlib();
env.add_macro(Macro::global("f", 1, takes_then_declines))
.unwrap();
for source in ["f(1)", "f(a)"] {
let errors = env.parser().parse(source).unwrap_err().errors;
let messages: Vec<_> = errors.iter().map(|e| e.msg.as_str()).collect();
assert_eq!(
messages,
["macro 'f' declined the call after taking from it"],
"{source}"
);
}
}
fn declines(
_: &mut crate::parser::MacroExprHelper<'_>,
_: &mut Option<crate::IdedExpr>,
_: &mut Vec<crate::IdedExpr>,
) -> Result<Option<crate::IdedExpr>, crate::ParseError> {
Ok(None)
}
#[test]
fn declining_a_call_with_a_broken_argument_reports_only_that_argument() {
let mut env = Env::stdlib();
env.add_macro(Macro::global("f", 1, declines)).unwrap();
let errors = env.parser().parse("f(has(1))").unwrap_err().errors;
let messages: Vec<_> = errors.iter().map(|e| e.msg.as_str()).collect();
assert_eq!(messages, ["invalid argument to has() macro"]);
}
#[test]
fn a_qualified_overload_declares_its_namespace() {
let mut env = Env::default();
env.add_overload("a.b.f", "a_b_f", vec![], noop).unwrap();
env.add_overload("g", "g", vec![], noop).unwrap();
env.add_member_overload("c.m", "c_m", types::INT_TYPE, vec![], noop)
.unwrap();
assert!(env.has_namespace("a"));
assert!(!env.has_namespace("a.b"));
assert!(!env.has_namespace("g"));
assert!(!env.has_namespace("c"), "member overloads aren't qualified");
}
#[test]
fn the_standard_library_registers_its_types() {
let names = [
"bool",
"bytes",
"double",
"int",
"list",
"map",
"null_type",
"optional_type",
"string",
"type",
"uint",
#[cfg(feature = "chrono")]
"google.protobuf.Duration",
#[cfg(feature = "chrono")]
"google.protobuf.Timestamp",
];
let stdlib = Env::stdlib();
let default = Env::default();
for name in names {
assert_eq!(stdlib.types().find_type(name).map(Type::name), Some(name));
assert!(default.types().find_type(name).is_none(), "{name}");
}
}
#[test]
fn add_type_rejects_another_type_of_a_registered_name() {
let mut env = Env::stdlib();
assert_eq!(
env.add_type(Type::new_opaque_type("optional_type")),
Err(DeclarationError::type_conflict("optional_type"))
);
assert_eq!(env.add_type(types::OPTIONAL_TYPE), Ok(()));
}
fn noop<'b, 'v>(args: Vec<CowVal<'b, 'v>>) -> Result<CowVal<'b, 'v>, crate::ExecutionError> {
Ok(args.into_iter().next().unwrap())
}
fn duplicate(function: &str, id: &str) -> DeclarationError {
DeclarationError::duplicate_overload(function, id)
}
#[test]
fn add_overload_rejects_a_duplicate_id() {
let mut env = Env::default();
assert_eq!(
env.add_overload("f", "f_int", vec![types::INT_TYPE], noop),
Ok(())
);
assert_eq!(
env.add_overload("f", "f_int", vec![types::STRING_TYPE], noop),
Err(duplicate("f", "f_int"))
);
}
#[test]
fn add_overload_rejects_a_duplicate_signature() {
let mut env = Env::default();
assert_eq!(
env.add_overload("f", "f_int", vec![types::INT_TYPE], noop),
Ok(())
);
assert_eq!(
env.add_overload("f", "other_id", vec![types::INT_TYPE], noop),
Err(duplicate("f", "other_id"))
);
}
#[test]
fn add_member_overload_rejects_a_duplicate_id_or_signature() {
let mut env = Env::default();
assert_eq!(
env.add_member_overload("f", "int_f", types::INT_TYPE, vec![], noop),
Ok(())
);
assert_eq!(
env.add_member_overload("f", "int_f", types::STRING_TYPE, vec![], noop),
Err(duplicate("f", "int_f"))
);
assert_eq!(
env.add_member_overload("f", "other_id", types::INT_TYPE, vec![], noop),
Err(duplicate("f", "other_id"))
);
}
#[test]
fn a_global_and_a_member_overload_may_share_a_shape_but_not_an_id() {
let mut env = Env::default();
assert_eq!(
env.add_overload("f", "f_int", vec![types::INT_TYPE], noop),
Ok(())
);
assert_eq!(
env.add_member_overload("f", "int_f", types::INT_TYPE, vec![], noop),
Ok(())
);
assert_eq!(
env.add_member_overload("f", "f_int", types::STRING_TYPE, vec![], noop),
Err(duplicate("f", "f_int"))
);
}
#[test]
fn a_rejected_overload_is_not_declared() {
let mut env = Env::default();
env.add_overload("f", "f_int", vec![types::INT_TYPE], noop)
.unwrap();
assert!(env
.add_overload("f", "f_dup", vec![types::INT_TYPE], noop)
.is_err());
assert!(env
.add_overload("g", "f_int", vec![types::INT_TYPE], noop)
.is_ok());
let int: Box<dyn Val> = Box::new(crate::common::types::CelInt::from(1));
assert!(env.find_overload("f", &[CowVal::Owned(int)]).is_some());
assert!(env.has_overload("f") && env.has_overload("g"));
assert!(!env.has_member_overload("f"));
}
}