diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b3c587..e9236bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,34 +16,29 @@ ### Contributors +## [10.0.0](https://github.com/ISibboI/evalexpr/compare/9.1.0...10.0.0) - 2023-05-21 + +### Added + + * Builtin functions can now be disabled (#129) + +### Contributors + +My warmhearted thanks goes to: + + * [hexofyore](https://github.com/hexofyore) + ## [9.1.0](https://github.com/ISibboI/evalexpr/compare/9.0.0...9.1.0) - 2023-05-16 ### Added -* Builtin functions `contains` and `contains_any` (#127) + * Builtin functions `contains` and `contains_any` (#127) ### Contributors My warmhearted thanks goes to: -* [nickisyourfan](https://github.com/nickisyourfan) - -## [8.2.0](https://github.com/ISibboI/evalexpr/compare/8.1.0...8.2.0) - 2023-04-13 - -### Added - -* `EvalExprError` now derives `Clone` (#116) - -### Changed - -* Occurrences of `f64` and `i64` have been replaced with the type aliases `FloatType` and `IntType` where applicable (#113) - -### Contributors - -My warmhearted thanks goes to: - -* [Natan Freeman](https://github.com/NatanFreeman) -* [Claus Matzinger](https://github.com/celaus) + * [nickisyourfan](https://github.com/nickisyourfan) ## [9.0.0](https://github.com/ISibboI/evalexpr/compare/8.2.0...9.0.0) - 2023-04-13 diff --git a/Cargo.toml b/Cargo.toml index ca89b04..c497c77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "evalexpr" -version = "9.1.0" +version = "10.0.0" description = "A powerful arithmetic and boolean expression evaluator" keywords = ["expression", "evaluate", "evaluator", "arithmetic", "boolean"] categories = ["parsing", "game-engines"] diff --git a/README.md b/README.md index 62e290e..81bf0f0 100644 --- a/README.md +++ b/README.md @@ -334,7 +334,20 @@ For more information about user-defined functions, refer to the respective [sect ### Builtin Functions -This crate offers a set of builtin functions. +This crate offers a set of builtin functions (see below for a full list). +They can be disabled if needed as follows: + +```rust +use evalexpr::*; +let mut context = HashMapContext::new(); +assert_eq!(eval_with_context("max(1,3)",&context),Ok(Value::from(3))); +context.set_builtin_functions_disabled(true).unwrap(); // Do proper error handling here +assert_eq!(eval_with_context("max(1,3)",&context),Err(EvalexprError::FunctionIdentifierNotFound(String::from("max")))); +``` + +Not all contexts support enabling or disabling builtin functions. +Specifically the `EmptyContext` has builtin functions disabled by default, and they cannot be enabled. +Symmetrically, the `EmptyContextWithBuiltinFunctions` has builtin functions enabled by default, and they cannot be disabled. | Identifier | Argument Amount | Argument Types | Description | |----------------------|-----------------|-------------------------------|-------------| diff --git a/src/context/mod.rs b/src/context/mod.rs index e05f893..041f678 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -22,6 +22,13 @@ pub trait Context { /// Calls the function that is linked to the given identifier with the given argument. /// If no function with the given identifier is found, this method returns `EvalexprError::FunctionIdentifierNotFound`. fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult; + + /// Checks if builtin functions are disabled. + fn are_builtin_functions_disabled(&self) -> bool; + + /// Disables builtin functions if `disabled` is `true`, and enables them otherwise. + /// If the context does not support enabling or disabling builtin functions, an error is returned. + fn set_builtin_functions_disabled(&mut self, disabled: bool) -> EvalexprResult<()>; } /// A context that allows to assign to variables. @@ -66,6 +73,7 @@ pub trait GetFunctionContext: Context { }*/ /// A context that returns `None` for each identifier. +/// Builtin functions are disabled and cannot be enabled. #[derive(Debug, Default)] pub struct EmptyContext; @@ -79,6 +87,20 @@ impl Context for EmptyContext { identifier.to_string(), )) } + + /// Builtin functions are always disabled for `EmptyContext`. + fn are_builtin_functions_disabled(&self) -> bool { + true + } + + /// Builtin functions can't be enabled for `EmptyContext`. + fn set_builtin_functions_disabled(&mut self, disabled: bool) -> EvalexprResult<()> { + if disabled { + Ok(()) + } else { + Err(EvalexprError::BuiltinFunctionsCannotBeEnabled) + } + } } impl<'a> IterateVariablesContext<'a> for EmptyContext { @@ -94,6 +116,50 @@ impl<'a> IterateVariablesContext<'a> for EmptyContext { } } +/// A context that returns `None` for each identifier. +/// Builtin functions are enabled and cannot be disabled. +#[derive(Debug, Default)] +pub struct EmptyContextWithBuiltinFunctions; + +impl Context for EmptyContextWithBuiltinFunctions { + fn get_value(&self, _identifier: &str) -> Option<&Value> { + None + } + + fn call_function(&self, identifier: &str, _argument: &Value) -> EvalexprResult { + Err(EvalexprError::FunctionIdentifierNotFound( + identifier.to_string(), + )) + } + + /// Builtin functions are always enabled for EmptyContextWithBuiltinFunctions. + fn are_builtin_functions_disabled(&self) -> bool { + false + } + + /// Builtin functions can't be disabled for EmptyContextWithBuiltinFunctions. + fn set_builtin_functions_disabled(&mut self, disabled: bool) -> EvalexprResult<()> { + if disabled { + Err(EvalexprError::BuiltinFunctionsCannotBeDisabled) + } else { + Ok(()) + } + } +} + +impl<'a> IterateVariablesContext<'a> for EmptyContextWithBuiltinFunctions { + type VariableIterator = iter::Empty<(String, Value)>; + type VariableNameIterator = iter::Empty; + + fn iter_variables(&self) -> Self::VariableIterator { + iter::empty() + } + + fn iter_variable_names(&self) -> Self::VariableNameIterator { + iter::empty() + } +} + /// A context that stores its mappings in hash maps. /// /// *Value and function mappings are stored independently, meaning that there can be a function and a value with the same identifier.* @@ -105,6 +171,9 @@ pub struct HashMapContext { variables: HashMap, #[cfg_attr(feature = "serde_support", serde(skip))] functions: HashMap, + + /// True if builtin functions are disabled. + without_builtin_functions: bool, } impl HashMapContext { @@ -128,6 +197,15 @@ impl Context for HashMapContext { )) } } + + fn set_builtin_functions_disabled(&mut self, disabled: bool) -> EvalexprResult<()> { + self.without_builtin_functions = disabled; + Ok(()) + } + + fn are_builtin_functions_disabled(&self) -> bool { + self.without_builtin_functions + } } impl ContextWithMutableVariables for HashMapContext { diff --git a/src/error/display.rs b/src/error/display.rs index 61e53b9..d7a4e75 100644 --- a/src/error/display.rs +++ b/src/error/display.rs @@ -113,6 +113,12 @@ impl fmt::Display for EvalexprError { regex, message ), ContextNotMutable => write!(f, "Cannot manipulate context"), + BuiltinFunctionsCannotBeEnabled => { + write!(f, "This context does not allow enabling builtin functions") + }, + BuiltinFunctionsCannotBeDisabled => { + write!(f, "This context does not allow disabling builtin functions") + }, IllegalEscapeSequence(string) => write!(f, "Illegal escape sequence: {}", string), CustomMessage(message) => write!(f, "Error: {}", message), } diff --git a/src/error/mod.rs b/src/error/mod.rs index 5154d6f..e896973 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -203,6 +203,12 @@ pub enum EvalexprError { /// An escape sequence within a string literal is illegal. IllegalEscapeSequence(String), + /// This context does not allow enabling builtin functions. + BuiltinFunctionsCannotBeEnabled, + + /// This context does not allow disabling builtin functions. + BuiltinFunctionsCannotBeDisabled, + /// A custom error explained by its message. CustomMessage(String), } diff --git a/src/lib.rs b/src/lib.rs index bc0dd29..76c74ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -317,7 +317,20 @@ //! //! ### Builtin Functions //! -//! This crate offers a set of builtin functions. +//! This crate offers a set of builtin functions (see below for a full list). +//! They can be disabled if needed as follows: +//! +//! ```rust +//! use evalexpr::*; +//! let mut context = HashMapContext::new(); +//! assert_eq!(eval_with_context("max(1,3)",&context),Ok(Value::from(3))); +//! context.set_builtin_functions_disabled(true).unwrap(); // Do proper error handling here +//! assert_eq!(eval_with_context("max(1,3)",&context),Err(EvalexprError::FunctionIdentifierNotFound(String::from("max")))); +//! ``` +//! +//! Not all contexts support enabling or disabling builtin functions. +//! Specifically the `EmptyContext` has builtin functions disabled by default, and they cannot be enabled. +//! Symmetrically, the `EmptyContextWithBuiltinFunctions` has builtin functions enabled by default, and they cannot be disabled. //! //! | Identifier | Argument Amount | Argument Types | Description | //! |----------------------|-----------------|-------------------------------|-------------| @@ -539,7 +552,7 @@ extern crate serde_derive; pub use crate::{ context::{ Context, ContextWithMutableFunctions, ContextWithMutableVariables, EmptyContext, - HashMapContext, IterateVariablesContext, + EmptyContextWithBuiltinFunctions, HashMapContext, IterateVariablesContext, }, error::{EvalexprError, EvalexprResult}, function::Function, diff --git a/src/operator/mod.rs b/src/operator/mod.rs index 218e7ad..386357a 100644 --- a/src/operator/mod.rs +++ b/src/operator/mod.rs @@ -459,7 +459,9 @@ impl Operator { let arguments = &arguments[0]; match context.call_function(identifier, arguments) { - Err(EvalexprError::FunctionIdentifierNotFound(_)) => { + Err(EvalexprError::FunctionIdentifierNotFound(_)) + if !context.are_builtin_functions_disabled() => + { if let Some(builtin_function) = builtin_function(identifier) { builtin_function.call(arguments) } else { diff --git a/tests/integration.rs b/tests/integration.rs index 2665362..9e8cf38 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1520,12 +1520,39 @@ fn test_type_errors_in_binary_operators() { #[test] fn test_empty_context() { - let context = EmptyContext; + let mut context = EmptyContext; assert_eq!(context.get_value("abc"), None); assert_eq!( context.call_function("abc", &Value::Empty), Err(EvalexprError::FunctionIdentifierNotFound("abc".to_owned())) ); + assert_eq!( + eval_with_context("max(1,3)", &context), + Err(EvalexprError::FunctionIdentifierNotFound(String::from( + "max" + ))) + ); + assert_eq!(context.set_builtin_functions_disabled(true), Ok(())); + assert_eq!( + context.set_builtin_functions_disabled(false), + Err(EvalexprError::BuiltinFunctionsCannotBeEnabled) + ) +} + +#[test] +fn test_empty_context_with_builtin_functions() { + let mut context = EmptyContextWithBuiltinFunctions; + assert_eq!(context.get_value("abc"), None); + assert_eq!( + context.call_function("abc", &Value::Empty), + Err(EvalexprError::FunctionIdentifierNotFound("abc".to_owned())) + ); + assert_eq!(eval_with_context("max(1,3)", &context), Ok(Value::Int(3))); + assert_eq!(context.set_builtin_functions_disabled(false), Ok(())); + assert_eq!( + context.set_builtin_functions_disabled(true), + Err(EvalexprError::BuiltinFunctionsCannotBeDisabled) + ); } #[test] @@ -2248,3 +2275,19 @@ fn test_negative_power() { assert_eq!(eval("(-3)^-2"), Ok(Value::Float(1.0 / 9.0))); assert_eq!(eval("-(3^-2)"), Ok(Value::Float(-1.0 / 9.0))); } + +#[test] +fn test_builtin_functions_context() { + let mut context = HashMapContext::new(); + // Builtin functions are enabled by default for HashMapContext. + assert_eq!(eval_with_context("max(1,3)", &context), Ok(Value::from(3))); + // Disabling builtin function in Context. + context.set_builtin_functions_disabled(true).unwrap(); + // Builtin functions are disabled and using them returns an error. + assert_eq!( + eval_with_context("max(1,3)", &context), + Err(EvalexprError::FunctionIdentifierNotFound(String::from( + "max" + ))) + ); +}