use std::collections::HashMap;
use std::sync::Arc;
use crate::CustomOperator;
use crate::config::EvaluationConfig;
use crate::engine::Engine;
#[derive(Clone)]
#[must_use = "the builder is consumed by `.build()`"]
pub struct EngineBuilder {
settings: EngineSettings,
operators: HashMap<String, Arc<dyn CustomOperator>>,
}
pub(crate) const ALL_FAMILIES: u32 = u32::MAX;
#[derive(Clone, Debug)]
pub(crate) struct EngineSettings {
pub(crate) config: EvaluationConfig,
pub(crate) templating: bool,
pub(crate) template_key_escape: Option<char>,
pub(crate) constant_folding: bool,
pub(crate) families: u32,
pub(crate) checked_names: Vec<String>,
}
impl Default for EngineSettings {
fn default() -> Self {
Self {
config: EvaluationConfig::default(),
templating: false,
template_key_escape: None,
constant_folding: true,
families: ALL_FAMILIES,
checked_names: Vec::new(),
}
}
}
impl Default for EngineBuilder {
fn default() -> Self {
Self::new()
}
}
impl EngineBuilder {
#[inline]
pub fn new() -> Self {
Self {
settings: EngineSettings::default(),
operators: HashMap::new(),
}
}
#[inline]
#[must_use = "builder methods return a new builder; chain into `.build()`"]
pub fn with_config(mut self, config: EvaluationConfig) -> Self {
self.settings.config = config;
self
}
#[inline]
#[must_use = "builder methods return a new builder; chain into `.build()`"]
pub fn with_templating(mut self, on: bool) -> Self {
self.settings.templating = on;
self
}
#[inline]
#[must_use = "builder methods return a new builder; chain into `.build()`"]
pub fn with_template_key_escape(mut self, prefix: char) -> Self {
self.settings.template_key_escape = Some(prefix);
self
}
#[cfg_attr(feature = "trace", doc = "")]
#[cfg_attr(
feature = "trace",
doc = "The trace surface ([`crate::Engine::trace`]) always disables folding"
)]
#[cfg_attr(
feature = "trace",
doc = "internally regardless of this setting, since traces would otherwise"
)]
#[cfg_attr(feature = "trace", doc = "lose the folded operators as steps.")]
#[inline]
#[must_use = "builder methods return a new builder; chain into `.build()`"]
pub fn with_constant_folding(mut self, on: bool) -> Self {
self.settings.constant_folding = on;
self
}
#[inline]
#[must_use = "builder methods return a new builder; chain into `.build()`"]
pub fn add_operator<T>(mut self, name: impl Into<String>, operator: T) -> Self
where
T: CustomOperator + 'static,
{
self.operators.insert(name.into(), Arc::new(operator));
self
}
pub fn try_add_operator<T>(self, name: impl Into<String>, operator: T) -> crate::Result<Self>
where
T: CustomOperator + 'static,
{
let name = name.into();
self.check_operator_name(&name)?;
let mut builder = self.add_operator(name.clone(), operator);
if !builder.settings.checked_names.contains(&name) {
builder.settings.checked_names.push(name);
}
Ok(builder)
}
pub fn check_operator_name(&self, name: &str) -> crate::Result<()> {
if let Some(builtin) = crate::engine::builtin_in(self.settings.families, name) {
return Err(crate::Error::configuration_error(format!(
"custom operator `{name}` would never run: the built-in operator `{}` answers to that name",
builtin.as_str()
)));
}
if let Some(escape) = self.settings.template_key_escape
&& name.starts_with(escape)
{
return Err(crate::Error::configuration_error(format!(
"custom operator `{name}` would never run in a template: it begins with the template key escape `{escape}`"
)));
}
Ok(())
}
pub fn check_operator_names(&self) -> crate::Result<()> {
self.settings
.checked_names
.iter()
.try_for_each(|name| self.check_operator_name(name))
}
#[must_use = "builder methods return a new builder; chain into `.build()`"]
pub fn with_families(mut self, families: impl IntoIterator<Item = crate::Family>) -> Self {
self.settings.families = families
.into_iter()
.fold(crate::Family::Core.bit(), |set, f| set | f.bit());
self
}
pub(crate) fn from_engine_parts(
settings: EngineSettings,
operators: HashMap<String, Arc<dyn CustomOperator>>,
) -> Self {
Self {
settings,
operators,
}
}
pub fn try_build(self) -> crate::Result<Engine> {
self.check_operator_names()?;
if self.settings.config.max_recursion_depth == 0 {
return Err(crate::Error::configuration_error(format!(
"max_recursion_depth must be an integer between 1 and {}",
u32::MAX
)));
}
Ok(self.build())
}
pub fn build(self) -> Engine {
Engine::from_builder_parts(self.settings, self.operators)
}
}